विकिपीडिया bhwiki https://bh.wikipedia.org/wiki/%E0%A4%AE%E0%A5%81%E0%A4%96%E0%A5%8D%E0%A4%AF_%E0%A4%AA%E0%A4%A8%E0%A5%8D%E0%A4%A8%E0%A4%BE MediaWiki 1.47.0-wmf.5 first-letter मीडिया विशेष वार्तालाप प्रयोगकर्ता प्रयोगकर्ता वार्ता विकिपीडिया विकिपीडिया वार्ता चित्र चित्र वार्ता मीडियाविकि मीडियाविकि वार्ता टेम्पलेट टेम्पलेट वार्ता मदद मदद वार्ता श्रेणी श्रेणी वार्ता TimedText TimedText talk Module Module talk Event Event talk शिक्षा 0 4952 797109 796131 2026-06-08T19:55:59Z SM7 3953 सुधार कइल गइल 797109 wikitext text/x-wiki {{for2|एकेडमिक अध्ययन के बिसय|[[शिक्षाशास्त्र]]|अउरी अइसन बिसय सभ|[[शिक्षा बिज्ञान]]}} [[चित्र:Schoolgirls in Bamozai.JPG|thumb|alt=हाथ में कापी किताब लिहले, बइठ के ऊपर देखत कई ठे लड़िकी सभ|[[अफ़ग़ानिस्तान|अफगानिस्तान]] में एगो [[इस्कूल]] में शिक्षा लेत लड़की सभ।]] '''शिक्षा''' ({{Lang|en|education}}), ''एजुकेशन'' भा ''एडुकेशन'') अइसन क्रिया ह जेह में कौनों तरह के ज्ञान, कौशल, नैतिक मूल्य, बिस्वास, आ आदत सिखावे के ब्यवस्था कइल जाला। शिक्षा के माध्यम के रूप में इस्कूली पढ़ाई भर नइखे सामिल, बलुक खीसा-कहनी से ले के चर्चा-परिचर्चा आ बीडियो देखावल भी सामिल हो सके ला। शिक्षा अक्सरहा केहू पढ़ावे सिखावे वाला ब्यक्ति के द्वारा दिहल जाले, जेकरा के शिक्षक कहल जाला; हालाँकि, कुछ लोग बिना केहू शिक्षक के भी सीख सकत बा।<ref>{{cite book | last = Dewey | first = John | title = Democracy and Education | publisher = The Free Press | date = 1944 | orig-year = 1916 | pages = 1–4 | isbn = 0-684-83631-9}}</ref> शिक्षा औपचारिक आ अनौपचारिक रूप से हो सके ले। मतलब कि ब्यवस्थित तरीका से शिक्षा देवे के मकसद से भी ई काम कइल जा सके आ आ ब्यक्ति अपना अनुभव से भा देख-देख के भी अपने बिचार आ ज्ञान में बदलाव ले आ के शिक्षा हासिल क सके ला। ब्यवस्थित शिक्षा के चार भाग में बाँटल जाला, नर्सरी/किंडरगार्डेन ([[इस्कूल]] से पहिले), [[प्राइमरी इस्कूल]] में, माध्यमिक इस्कूल में, [[कॉलेज]] या [[यूनिवर्सिटी]] में या अप्रेंटिस के रूप में। शिक्षा के अधिकार, के अरथ हवे एक ठो ख़ास उमिर के लड़िकन के शिक्षा हासिल करे के कानूनी अधिकार।<ref name="ICESCR-art13.1">''ICESCR'', Article 13.1</ref> बहुत सारा देसन में कुछ उमिर तक के लड़िका-बच्चा सभ खातिर शिक्षा अनिवार्य कइल गइल बाटे। एगो दुसरे सेंस में, अंग्रेजी के ''एजुकेशन'' शब्द के इस्तेमाल ज्ञान के ओह शाखा भा एकेडमिक बिसय खातिर कइल जाला जे पढ़ाई आ पढ़ावे सिखावे के अध्ययन करे ला — परंपरागत रूप से एह बिज्ञान के [[शिक्षाशास्त्र]] भा ''पेडागोजी'' कहल जाला; जबकि [[शिक्षा बिज्ञान]] सभ में कई गो अकेडमिक बिसय सामिल बाड़ें। {{clear}} == इहो देखल जाय == * [[जानकारी]] * [[विद्यालय]] * [[शिक्षाशास्त्र]] * [[शिक्षा बिज्ञान]] * [[भारतीय शिक्षा मंडल]] == संदर्भ == {{Reflist|35em}} [[श्रेणी:शिक्षा]] [[श्रेणी:शिक्षाशास्त्र]] {{Edu-stub}} 62qdpny7iud713dg0bvlgzpil9f4yb9 विद्यापति 0 8290 797101 763944 2026-06-08T14:31:20Z SM7 3953 बिस्तार कइल गइल, अनुबाद कइल गइल 797101 wikitext text/x-wiki {{Infobox writer | name = विद्यापति | native_name = | native_name_lang = | image = Stamp of India - 1965 - Colnect 371672 - Vidyapati Commemoration.jpeg | image_size = 240px | alt = एगो पुरुष के चित्र सोझा देखत | caption = भारत के 1965 में जारी डाक टिकट पर बिद्यापति | pseudonym = | birth_date = 1352<ref name="ignca"/><ref name="britannica"/> | birth_place = बिस्फी, मधुबनी<ref name="britannica"/> (वर्तमान में [[भारत]] के [[बिहार]] राज्य में) | death_date = 1448<ref name="ignca"/> | death_place = बिस्फी, मधुबनी, बिहार<ref name="britannica"/> <!-- <br/> विद्यापति नगर (वर्तमान नाँव), वर्तमान समस्तीपुर, बिहार, भारत<br/> [[जनकपुर]] (निर्वास में) (वर्तमान में [[नेपाल]] के [[जनकपुर]]) --><!-- ई जगह सभ के बारे में परमान मिले तब जोड़ीं --> | resting_place = <!-- इनके दफ्नावल ना गइल, एह पैरामीटर के मत जोड़ीं --> | occupation = [[कवी]] | language = मैथिली, अवहट्ट, संस्कृत | nationality = [[भारत के लोग|भारतीय]] | period = मध्यजुग | genre = पद, गीत | subjects = भक्ति, शृंगार | movement = | notableworks = ''कीर्तिलता'', ''पदावली'' | years_active = | module = | website = }} '''विद्यापति''' (1352–1448), चाहे '''बिद्यापति''', एगो भारतीय कवी रहलें जे [[मैथिली]], [[अवहट्ट]] आ [[संस्कृत]] में आपन रचना कइलें, प्रमुख रूप से इनके मैथिली के रचना खाती जानल-मानल जाला आ मैथिली भाषा के आदि कवी आ "मैथिल कोकिल" के उपाधी दिहल जाला। कबिता के अलावा संस्कृत में गद्य लेखन के काम भी कइलेन। भक्ति आ शृंगार इनके रचना सभ के मुख्य बिसय रहल आ कबिता के रूप गीत आ पद रहल। [[शिव]]-[[पार्वती|पारबती]] आ [[राधा]]-[[कृष्ण]] दुनों इनके भक्ति वाली रचना के बिसय बनल लोग। इनके साहित्यिक परभाव बाद के [[हिंदी भाषा|हिंदुस्तानी भाषा]], मैथिली भाषा आ [[बंगाली भाषा|बंगाली]] भाषा के साहित्य पर सीधा-सीधा, आ अप्रत्यक्ष रूप से [[नेपाली भाषा|नेपाली]], [[ओडिया]] आ [[असमिया भाषा|असमिया]] भाषा के साहित्य पर पड़ल। विद्यापति के समय अइसन रहे जेह समय साहित्य आ संपर्क के भाषा अवहट्ट रहल आ वर्तमान मैथिली, बंगाली वगैरह देसी भाषा सभ के बिकास सुरू भइल रहल, एही से इनके रचना सभ के परभाव, जे देसी बोली के साहित्य के भाषा बना के रचल गइली सऽ, एह सगरी पूरबी भाषा सभ पर परल। एही कारन बिद्यापति के भारतीय साहित्य में लगभग उहे दर्जा दिहल जाला जे इटली में [[दांते]] के भा [[इंग्लैंड]] में [[ज्यॉफ्री चॉसर|चॉसर]] के दिहल जाला।{{sfn|विद्यापति ठाकुर|1979|p=1}} इनके मैथिलि रचना सभ प आधारित बिदापती नाच बाद के समय में बिहार-नेपाल के मिथिला क्षेत्र के खास बिधा के रूप में अस्थापित भइल। विद्यापति के मैथिली गीत सभ आज भी [[लोकगीत]] के रूप में सुनल-गावल जालें आ इनके रचना साहित्य में उच्च-कक्षा सभ में पढ़ावल जालीं। ==समय== विद्यापति के जनम भा निधन के बारे में लिखित रूप से कुछ ना मिले ला, एही कारन इनके समय अनुमान के बिसय हवे{{sfn|शिवप्रसाद सिंह|1999|p=1}} आ कई किसिम के अनुमान कई आधार प लगावल जाला। उपलब्ध साक्ष्य के हवाला से, [[रामधारी सिंह दिनकर]] इनके जनम 1350 ईसवी के आसपास भइल होखी अइसन लिखे लें।{{sfn|रामधारी सिंह दिनकर|2008|p=11}} डाकटर सुभद्र झा के मत के अनुसार, विद्यापति के काल 1352 ईसवी से 1448 ईसवी हवे।<ref name="ignca">{{cite web|last1=मिश्र|first1=पूनम|title=विद्यापति: कृतित्व एवं जीवन, एक परिचय|url=http://www.ignca.nic.in/coilnet/vp001.htm|website=ignca.nic.in|publisher=Indira Gandhi National Centre for the Arts|accessdate=5 मार्च 2018|language=hi}}</ref> इहे तारीख अंग्रेजी के प्रसिद्ध ज्ञानकोश ब्रिटैनिका एनसाइक्लोपीडिया प भी मिले ला।<ref name="britannica">{{cite web|title=Vidyapati: Indian writer and poet|url=https://www.britannica.com/biography/Vidyapati-Indian-writer-and-poet|website=britannica.com|publisher=एन्साइक्लोपीडिया ब्रिटैनिका (ऑनलाइन)|accessdate=5 मार्च 2018|language=en}}</ref> सभसे बिशद बिबरन प्रस्तुत कइले बाने शिवप्रसाद सिंह, जे विद्यापति प किताब लिखलें।{{sfn|शिवप्रसाद सिंह|2007|pp=46-55}} इनके द्वारा सोझा रखल गइल बिबिध मत के तुलना अनुसार, विद्यापति के रचना ''कीर्तिलता'' में बिबरन मिले ला कि राजा गणेश्वर के निधन लक्ष्मण संवत 252 में भइल आ ई अनुमान मानल जाला कि एह समय विद्यापति के उमिर दस बारह बरिस के रहल, यानी कि इनके जनम लगभग 242 लक्ष्मण संवत में भइल होखी। समस्या ई बा कि लक्ष्मण संवत कब सुरू भइल एहू में बिबाद बा। कुछ अनुमान के मोताबिक एह परमान के आधार प विद्यापति के जनम के तिथी 1360 ईसवी के आसपास मान लिहल गइल, काहें कि लक्ष्मण संवत के सुरुआत पर अलग-अलग मत के अनुसार 1106 से 1119 ईसवी मानल जाला आ एह तरीका से गणेश्वर के निधन के तिथी 1358 से 1371 ईसवी के बिचा में ठहरे ले। हालाँकि, शिवप्रसाद सिंह ''कीर्तिलता'' के आधार पर विद्यापति के समय के निर्धारण के ठीक ना बुझे लें आ कई लोगन के मत के परिच्छा करे के बाद आपन मत देलें कि इनके जनम 1373 ईसवी के आसपास भइल होखे ई संभव बा।{{sfn|शिवप्रसाद सिंह|2007|p=51}}{{sfn|शिवप्रसाद सिंह|1999|p=194}}<ref name="ignca" /> एही तरह से इनके निधन के तिथी के बारे में भी अनुमाने लगावल जाला। लखनसेन नाँव के कवी के कविता के हवाला से अनुमान लगावे पर शिवप्रसाद सिंह बतावे लें कि एह आधार पर विद्यापति के निधन 1424 ईसवी के आसपास ठहरे ला; हालाँकि ऊ खुदे एह बारे में लिखे लें कि ई विद्यापति के अंतिम समय मानल ठीक ना बुझाला। कुछ जगह ई बिबरन मिले ला कि विद्यापति लक्ष्मण संवत 299 (1418 ईसवी, अगर लक्ष्मण संवत के सुरुआत 1119 ईसवी मानल जाय) में ''लिखनावली'' ग्रंथ पूरा कइलेन आ 309 में भागवत के एगो प्रति लिख के पूरा कइलें, यानी एह आधार प ऊ 1428 ईसवी तक जियत रहलें। एही कारण सिंह, लखनसेन के आधार पर 1424 वाल मत ठीक ना माने लें, बस एकरा के एगो मत के रूप में लोगन के सोझा रखे लें।{{sfn|शिवप्रसाद सिंह|1999|p=198}} अन्य कथा के हवाला दे के लिखे लें कि राजा शिवसिंह के निधन के बत्तीस बरिस बाद विद्यापति एगो सपना देखलें आ उनके आपन मउअत नगीचे बुझाए लागल, यानी शिवसिंह के निधन भइल 1415 में आ एह में 32 जोड़ल जाय तब विद्यापति के निधन 1447 ईसवी के कुछ समय बाद भइल होखी।{{sfn|शिवप्रसाद सिंह|1999|p=196}}<ref name="ignca" /> अउरी दूसर मत सभ में डॉ. बिमानबिहारी मजुमदार विद्यापति के जनम 1380 ईसवी आ निधन 1460 के बाद कबो भइल माने लें; नगेन्द्रनाथ गुप्त 1440 के इनके निधन तिथी माने लें आ उमेश मिश्र इनके निधन के तिथी 1466 के बाद ले माने लें।<ref name="ignca" /> हालाँकि, मजुमदार अपना बिचार में 1460 के बाद बिद्यापति के होखे के बात के खंडन करे लें। मजुमदार के मोताबिक विद्यापति के जिनगी के महत्व वाला घटना सभ के क्रम बा: 1380 के आसपास इनके जनम, 1395-96 ईसवी के आसपास पद लिख के गियासुद्दीन आ नसरत शाह के समर्पित कइल, 1397 में सुलतान जौनपुर द्वारा तिरहुत जीतल गइल जेकरे पहिले ई दुनों पद लिखल गइल रहलें; 1400 के आसपास ''भूपरिक्रमा'' के रचना, 1402-04 के बीच इब्राहिम शाह द्वारा तिरहुत के सिंघासन पर कीर्तिसिंह के स्थापित कइल आ ओही समय के आसपास ''कीर्तिलता'' के रचना; 1410 से 1414 के बीच शिवसिंह के राज्यकाल में दू सौ पद सभ के रचना; 1440 से 1460 के बीच ''विभागसागर'', ''दान-वाक्यावली'', आ ''दुर्गाभक्ति तरंगिणी'' के रचना।{{sfn|शिवप्रसाद सिंह|2007|pp=53-54}} [[हिंदी साहित्य]] के परंपरा में, विद्यापति के समय "आदिकाल" में परे ला। आदिकाल के दूसर नाँव "वीरगाथा काल" भी हवे, हालाँकि, विद्यापति के काब्य के बीरगाथा से कवनो तालमेल ना बा, साथे-साथ ई एह काल के समाप्ति के बाद ले रहलें अइसन भी बिचार कइल जाला, यानी लोग इनके आदिकाल में रखे पर संतोख ना करे ला। आदिकाल के बाद के समय के "भक्तिकाल" मानल जाला, जबकि रामचंद्र शुक्ल इनका के भक्ति वाला कवी ना माने लें।{{sfn|रामचंद्र शुक्ल|2010|p=37}} शुक्ल जी के अइसन बिचार के कारन विद्यापति के शृंगार प्रधान रचना बा।{{efn|शुकुल जी के कहनाम कोट कइल जाला कि "विद्यापति के पद अधिकतर शृंगार के ही हैं जिनमें नायिका और नायक राधा-कृष्ण हैं। इन पदों की रचना जयदेव के गीतकाव्य के अनुकरण पर ही शायद की गई है। इनका माधुर्य अद्भूत है। विद्यापति शैव थे। इन्होंने इन पदों की रचना शृंगार काव्य की दृष्टि से की है, भक्त के रूप में नहीं। विद्यापति को कृष्णभक्तों की परंपरा में नहीं समझना चाहिये।"<ref name="dustudy">{{cite web|title=विद्यापति भक्त या शृंगारिक कवि|url=http://vle.du.ac.in/mod/book/print.php?id=12922&chapterid=27770|website=du.ac.in|publisher=दिल्ली विश्वविद्यालय|accessdate=5 मार्च 2018|language=hi}}{{Dead link|date=September 2023 |bot=InternetArchiveBot |fix-attempted=yes }}</ref>}} जबकि [[हजारी प्रसाद द्विवेदी]] शुकुल जी के बिचार के निवारण करे लें आ विद्यापति के भक्ती वाली रचना सभ के पूरबी भाषा सभ के साहित्य पर बाद में परल परभाव के ओर धियान दिवावे लें।{{efn|हजारी प्रसाद जी के हवाला दिहल जाला की, "विद्यापति शृंगार रस के सिद्धवाक कवि थे। उनकी पदावली में राधा और कृष्ण की जिस प्रेमलीला का चित्रण है, वह अपूर्व है। इस वर्णन में प्रेम के शरीर पक्ष की प्रधानता अवश्य है पर इससे सहृदय के चित्त में विकार नहीं उत्पन्न होता बल्कि भावों की सांद्रता और अभिव्यक्ति की प्रेषणगुणिता के कारण वह बहुत ही आकर्षक हो गया है। ...राधा और कृष्ण के प्रेम प्रसंगों को यह पुस्तक प्रथम बार उत्तर भारत में गेय पदों में प्रकाशित करती है। इस पुस्तक के पदों ने आगे चलकर बंगाल, असम, और उड़ीसा के वैष्णव भक्तों को खूब प्रभावित किया और उन प्रदेशों के भक्ति साहित्य में नई प्रेरणा और प्राणधारा संचारित करने में समर्थ हुई। इसीलिए पूर्वी प्रदेशों में सर्वत्र यह पुस्तक धर्म ग्रंथ की महिमा पा सकी।"<ref name="dustudy" />}} कुल मिला के विद्यापति, अपना काब्य के बिसेस्ता के आधार पर ना त आदिकाल के कवी के रूप में साबित होखे लें ना भक्ति काल के बिसेस्ता उनुका रचना में निर्बिबाद रूप से खोजल जा सके ला। दिनकर के कहनाम बा की, ''"...विद्यापति कवनो बर्ग में ना समा सके लें। उनुके सत्कार खाती अइसन सिंघासन चाही जवना प खाली उहे बइठ सके लें। ऊ खाली कबी रहलें आ कबिता में सौंदर्य आ आनंद के छाड़ के ऊ अउरी कवनो बात के जगहा ना दें।"''{{efn|''"विद्यापति किसी भी वर्ग में नहीं समा सकते। उनके सत्कार के लिए ऐसा सिंहासन चाहिए जिस पर केवल वही बैठ सकते हैं। वे केवल कवि थे और कविता में सौन्दर्य और आनन्द को छोड़ कर वे किसी और बात को स्थान नहीं देते थे।"'' रामधारी सिंह दिनकर।{{sfn|रामधारी सिंह दिनकर|2008|p=12}} }} == रचना संसार == [[चित्र:Statue of Maha Kavi Kokil Vidyapati.jpg|thumb|alt=सीना से ऊपर के हिस्सा देखावत एगो मूर्ती|विद्यापति के एगो मुर्ती, बिस्फी, [[मधुबनी]], बिहार]] === पदावली === पदावली बिद्यापति के सभसे परसिद्ध रचना हवे। एह में गावल जा सके वाला सुघर पद बाड़ें। इनहन के बिसय शिव के भक्ती, राधा-कृष्ण के प्रेम, आम प्रेम आ सुंदरता के बखान बाटे। === अन्य रचना === अउरी बिबिध बिसय पर बिद्यापति कलम चलवलें जेह में मुख्य-मुख्य बाड़ें: * ''पुरुष परीक्षा'' — मुख्य रूप से नैतिक शिक्षा आ सदाचार संबंधी उपदेशन पर आधारित बा। हाल में प्रकाशन विभाग, [[भारत सरकार]] द्वारा एह ग्रंथ के हिंदी अनुबाद प्रकाशित कइल गइल बा, जेकर अनुबाद अखिलेश झा कइले बाड़ें। एह पुस्तक में मूल रचना के 44 कहानी सभ में से चुनल गइल 25 कहानी शामिल बाड़ी स। एकरे साथे पुस्तक में विद्यापति आ पुरुष परीक्षा दुनो पर विद्वत्तापूर्ण भूमिका (परिचयात्मक लेख) भी दिहल गइल बा। पुरुष परीक्षा के प्रमुख विषय नैतिक शिक्षा, मानव चरित्र के परख आ आदर्श आचरण के मार्गदर्शन हवे। * ''लिखनावली'' लेखन के काम के बारे में। * ''भू-परिक्रमा'', भूगोल के बारे में, एह में आसपास के जगह वगैरह के बरनन बाटे। * ''विभागसार'' आत्मकथा नियर रचना। * ''दानवाक्यावली'' दान के बारे में। * ''गंगावाक्यावली'' * ''वर्षकृत्य'' * ''दुर्गाभक्तितरंगिणी'' * ''शैवसर्वस्वहार'' * ''कीर्तिपताका'' * ''कीर्तिलता'' {{clear}} ==नोट आ टीका-टिप्पणी== <references group="नोट"/> ==संदर्भ== {{Reflist|35em}} ==संदर्भ ग्रंथ== {{refbegin}} * {{cite book|author=रामचंद्र शुक्ल |title=हिंदी साहित्य का इतिहास|url=|year=2010|publisher=लोकभारती प्रकाशन |place=इलाहाबाद}}<!-- {{sfn|रामचंद्र शुक्ल|2010|p=37}} --> * {{cite book|author=रामधारी सिंह दिनकर|title=कवि और कविता|url=https://books.google.com/books?id=9FqZPDa6NqcC&pg=PA11|year=2008|publisher=राजकमल प्रकाशन |isbn=978-81-8031-324-0}}<!-- {{sfn|रामधारी सिंह दिनकर|2008|p=}} --> * {{cite book|author=शिवप्रसाद सिंह|title=कीर्तिलता और अवहट्ट भाषा |url=https://books.google.com/books?id=TVkvD3EZaBIC&pg=PT209|year=1999|publisher=वाणी प्रकाशन|isbn=978-93-5000-020-5}} <!-- {{sfn|शिवप्रसाद सिंह|1999|p=}} --> * {{cite book|author=शिवप्रसाद सिंह|title=विद्यापति |url=https://books.google.com/books?id=0eWvGQAn7NwC |year=2007 |publisher=लोकभारती प्रकाशन ( अब राजकमल प्रकाशन) |place=इलाहाबाद|isbn=}} <!-- {{sfn|शिवप्रसाद सिंह|2007|p=}} --> * {{cite book|author=शुभकार कपूर|title=विद्यापति और उनका काव्य: महाकवि विद्यापति की काव्य-कला एवं जीवनाकृति |url=https://books.google.com/books?id=y7VHAAAAMAAJ|year=1966|publisher=गंगा पुस्तकमाला कार्यालय}}<!-- {{sfn|शुभकार कपूर|1966|p=}} --> * {{cite book|author=मोहन लाल|title=Encyclopaedia of Indian Literature: Sasay to Zorgot|url=https://books.google.com/books?id=KnPoYxrRfc0C&pg=PA4565|year=1992|publisher=साहित्य अकादमी|isbn=978-81-260-1221-3}}<!-- {{sfn|मोहन लाल|1992|p=}} --> * {{cite book|author=राम दयाल राकेश|title=Vidyapati, the Greatest Poet of Mithila|url=https://books.google.com/books?id=MbBIAQAAIAAJ|year=2007|publisher=Greater Janakpur Area Development Council|isbn=978-9937-2-0148-3}}<!-- {{sfn|राम दयाल राकेश|2007|p=}} --> * {{cite book|author=विद्यापति ठाकुर|title=Vidyapati Bangiya Padabali: Songs of the Love of Radha and Krishna |trans-title=विद्यापति बंगीय पदाबली: राधा अउरी कृष्ण के परेम-गीत |url=https://books.google.com/books?id=OqVDRnEf6SEC&pg=PT3|year=1979|publisher=Library of Alexandria|isbn=978-1-4655-1475-2}}<!-- {{sfn|विद्यापति ठाकुर|1979|p=1}} --> * {{cite book|author=राधाकृष्ण चौधरी|title=A Survey of Maithili Literature |trans-title=मैथिली साहित्य के सर्वेक्षण |url=https://books.google.com/books?id=C0f898HDLAYC&pg=PA51|year=1976|publisher=Ram Vilas Sahu|isbn=978-93-80538-36-5}} <!-- {{harv|राधाकृष्ण चौधरी|1976|p=}} --> {{refend}} ==बाहरी कड़ी== * [http://www.kavitakosh.org/kk/index.php?title=%E0%A4%B5%E0%A4%BF%E0%A4%A6%E0%A5%8D%E0%A4%AF%E0%A4%BE%E0%A4%AA%E0%A4%A4%E0%A4%BF विद्यापति कऽ रचना सभ] {{Webarchive|url=https://web.archive.org/web/20090504122744/http://www.kavitakosh.org/kk/index.php?title=%E0%A4%B5%E0%A4%BF%E0%A4%A6%E0%A5%8D%E0%A4%AF%E0%A4%BE%E0%A4%AA%E0%A4%A4%E0%A4%BF |date=2009-05-04 }}, कविता कोश में। {{In lang|hi}} * [http://www.cse.iitk.ac.in/~amit/books/vidyapati-1963-love-songs-of.html 27 poems] transl. Deben Bhattacharya, from ''Love Songs of Vidyapati'', (UNESCO) 1963. {{in lang|en}} * [https://hdl.handle.net/2027/coo1.ark:/13960/t6446799m {{IAST|Songs of the love of Rādhā and Krishna}}], translated into English by Ananda Coomaraswamy and Arun Sen 1915. {{in lang|en}} <!-- https://www.prabhatkhabar.com/news/63307.aspx बादमें इस्तेमाल खाती --> <!-- http://www.pupdepartments.ac.in/de/lesson/ug/ba/Semester%203/Hindi/L-%201-15%20Title.pdf बाद में इस्तेमाल खाती --> <!-- http://www.sahityasudha.com/articles_nov_2016/lekh/nayana_deliwala/lekh_adikaleen_kaviyon.html बाद में इस्तेमाल खाती --> {{Authority control}} [[श्रेणी:भारतीय कवि]] [[श्रेणी:मैथिली-भाषा के लेखक]] [[श्रेणी:मैथिली-भाषा के कवि]] [[श्रेणी:बिहार के लोग]] [[श्रेणी:हिंदी-भाषा के कवि]] [[श्रेणी:15वीं-सदी के भारतीय कवि]] [[श्रेणी:मध्यजुग के भारतीय कवि]] 3vunwu91ovh9obir8alktath3beh873 797102 797101 2026-06-08T14:32:30Z SM7 3953 +विकिकड़ी जोड़ल गइल 797102 wikitext text/x-wiki {{Infobox writer | name = विद्यापति | native_name = | native_name_lang = | image = Stamp of India - 1965 - Colnect 371672 - Vidyapati Commemoration.jpeg | image_size = 240px | alt = एगो पुरुष के चित्र सोझा देखत | caption = भारत के 1965 में जारी डाक टिकट पर बिद्यापति | pseudonym = | birth_date = 1352<ref name="ignca"/><ref name="britannica"/> | birth_place = बिस्फी, मधुबनी<ref name="britannica"/> (वर्तमान में [[भारत]] के [[बिहार]] राज्य में) | death_date = 1448<ref name="ignca"/> | death_place = बिस्फी, मधुबनी, बिहार<ref name="britannica"/> <!-- <br/> विद्यापति नगर (वर्तमान नाँव), वर्तमान समस्तीपुर, बिहार, भारत<br/> [[जनकपुर]] (निर्वास में) (वर्तमान में [[नेपाल]] के [[जनकपुर]]) --><!-- ई जगह सभ के बारे में परमान मिले तब जोड़ीं --> | resting_place = <!-- इनके दफ्नावल ना गइल, एह पैरामीटर के मत जोड़ीं --> | occupation = [[कवी]] | language = मैथिली, अवहट्ट, संस्कृत | nationality = [[भारत के लोग|भारतीय]] | period = मध्यजुग | genre = पद, गीत | subjects = [[भक्ति]], शृंगार | movement = | notableworks = ''कीर्तिलता'', ''पदावली'' | years_active = | module = | website = }} '''विद्यापति''' (1352–1448), चाहे '''बिद्यापति''', एगो भारतीय कवी रहलें जे [[मैथिली]], [[अवहट्ट]] आ [[संस्कृत]] में आपन रचना कइलें, प्रमुख रूप से इनके मैथिली के रचना खाती जानल-मानल जाला आ मैथिली भाषा के आदि कवी आ "मैथिल कोकिल" के उपाधी दिहल जाला। कबिता के अलावा संस्कृत में गद्य लेखन के काम भी कइलेन। [[भक्ति ]] आ शृंगार इनके रचना सभ के मुख्य बिसय रहल आ कबिता के रूप गीत आ पद रहल। [[शिव]]-[[पार्वती|पारबती]] आ [[राधा]]-[[कृष्ण]] दुनों इनके भक्ति वाली रचना के बिसय बनल लोग। इनके साहित्यिक परभाव बाद के [[हिंदी भाषा|हिंदुस्तानी भाषा]], मैथिली भाषा आ [[बंगाली भाषा|बंगाली]] भाषा के साहित्य पर सीधा-सीधा, आ अप्रत्यक्ष रूप से [[नेपाली भाषा|नेपाली]], [[ओडिया]] आ [[असमिया भाषा|असमिया]] भाषा के साहित्य पर पड़ल। विद्यापति के समय अइसन रहे जेह समय साहित्य आ संपर्क के भाषा अवहट्ट रहल आ वर्तमान मैथिली, बंगाली वगैरह देसी भाषा सभ के बिकास सुरू भइल रहल, एही से इनके रचना सभ के परभाव, जे देसी बोली के साहित्य के भाषा बना के रचल गइली सऽ, एह सगरी पूरबी भाषा सभ पर परल। एही कारन बिद्यापति के भारतीय साहित्य में लगभग उहे दर्जा दिहल जाला जे इटली में [[दांते]] के भा [[इंग्लैंड]] में [[ज्यॉफ्री चॉसर|चॉसर]] के दिहल जाला।{{sfn|विद्यापति ठाकुर|1979|p=1}} इनके मैथिलि रचना सभ प आधारित बिदापती नाच बाद के समय में बिहार-नेपाल के मिथिला क्षेत्र के खास बिधा के रूप में अस्थापित भइल। विद्यापति के मैथिली गीत सभ आज भी [[लोकगीत]] के रूप में सुनल-गावल जालें आ इनके रचना साहित्य में उच्च-कक्षा सभ में पढ़ावल जालीं। ==समय== विद्यापति के जनम भा निधन के बारे में लिखित रूप से कुछ ना मिले ला, एही कारन इनके समय अनुमान के बिसय हवे{{sfn|शिवप्रसाद सिंह|1999|p=1}} आ कई किसिम के अनुमान कई आधार प लगावल जाला। उपलब्ध साक्ष्य के हवाला से, [[रामधारी सिंह दिनकर]] इनके जनम 1350 ईसवी के आसपास भइल होखी अइसन लिखे लें।{{sfn|रामधारी सिंह दिनकर|2008|p=11}} डाकटर सुभद्र झा के मत के अनुसार, विद्यापति के काल 1352 ईसवी से 1448 ईसवी हवे।<ref name="ignca">{{cite web|last1=मिश्र|first1=पूनम|title=विद्यापति: कृतित्व एवं जीवन, एक परिचय|url=http://www.ignca.nic.in/coilnet/vp001.htm|website=ignca.nic.in|publisher=Indira Gandhi National Centre for the Arts|accessdate=5 मार्च 2018|language=hi}}</ref> इहे तारीख अंग्रेजी के प्रसिद्ध ज्ञानकोश ब्रिटैनिका एनसाइक्लोपीडिया प भी मिले ला।<ref name="britannica">{{cite web|title=Vidyapati: Indian writer and poet|url=https://www.britannica.com/biography/Vidyapati-Indian-writer-and-poet|website=britannica.com|publisher=एन्साइक्लोपीडिया ब्रिटैनिका (ऑनलाइन)|accessdate=5 मार्च 2018|language=en}}</ref> सभसे बिशद बिबरन प्रस्तुत कइले बाने शिवप्रसाद सिंह, जे विद्यापति प किताब लिखलें।{{sfn|शिवप्रसाद सिंह|2007|pp=46-55}} इनके द्वारा सोझा रखल गइल बिबिध मत के तुलना अनुसार, विद्यापति के रचना ''कीर्तिलता'' में बिबरन मिले ला कि राजा गणेश्वर के निधन लक्ष्मण संवत 252 में भइल आ ई अनुमान मानल जाला कि एह समय विद्यापति के उमिर दस बारह बरिस के रहल, यानी कि इनके जनम लगभग 242 लक्ष्मण संवत में भइल होखी। समस्या ई बा कि लक्ष्मण संवत कब सुरू भइल एहू में बिबाद बा। कुछ अनुमान के मोताबिक एह परमान के आधार प विद्यापति के जनम के तिथी 1360 ईसवी के आसपास मान लिहल गइल, काहें कि लक्ष्मण संवत के सुरुआत पर अलग-अलग मत के अनुसार 1106 से 1119 ईसवी मानल जाला आ एह तरीका से गणेश्वर के निधन के तिथी 1358 से 1371 ईसवी के बिचा में ठहरे ले। हालाँकि, शिवप्रसाद सिंह ''कीर्तिलता'' के आधार पर विद्यापति के समय के निर्धारण के ठीक ना बुझे लें आ कई लोगन के मत के परिच्छा करे के बाद आपन मत देलें कि इनके जनम 1373 ईसवी के आसपास भइल होखे ई संभव बा।{{sfn|शिवप्रसाद सिंह|2007|p=51}}{{sfn|शिवप्रसाद सिंह|1999|p=194}}<ref name="ignca" /> एही तरह से इनके निधन के तिथी के बारे में भी अनुमाने लगावल जाला। लखनसेन नाँव के कवी के कविता के हवाला से अनुमान लगावे पर शिवप्रसाद सिंह बतावे लें कि एह आधार पर विद्यापति के निधन 1424 ईसवी के आसपास ठहरे ला; हालाँकि ऊ खुदे एह बारे में लिखे लें कि ई विद्यापति के अंतिम समय मानल ठीक ना बुझाला। कुछ जगह ई बिबरन मिले ला कि विद्यापति लक्ष्मण संवत 299 (1418 ईसवी, अगर लक्ष्मण संवत के सुरुआत 1119 ईसवी मानल जाय) में ''लिखनावली'' ग्रंथ पूरा कइलेन आ 309 में भागवत के एगो प्रति लिख के पूरा कइलें, यानी एह आधार प ऊ 1428 ईसवी तक जियत रहलें। एही कारण सिंह, लखनसेन के आधार पर 1424 वाल मत ठीक ना माने लें, बस एकरा के एगो मत के रूप में लोगन के सोझा रखे लें।{{sfn|शिवप्रसाद सिंह|1999|p=198}} अन्य कथा के हवाला दे के लिखे लें कि राजा शिवसिंह के निधन के बत्तीस बरिस बाद विद्यापति एगो सपना देखलें आ उनके आपन मउअत नगीचे बुझाए लागल, यानी शिवसिंह के निधन भइल 1415 में आ एह में 32 जोड़ल जाय तब विद्यापति के निधन 1447 ईसवी के कुछ समय बाद भइल होखी।{{sfn|शिवप्रसाद सिंह|1999|p=196}}<ref name="ignca" /> अउरी दूसर मत सभ में डॉ. बिमानबिहारी मजुमदार विद्यापति के जनम 1380 ईसवी आ निधन 1460 के बाद कबो भइल माने लें; नगेन्द्रनाथ गुप्त 1440 के इनके निधन तिथी माने लें आ उमेश मिश्र इनके निधन के तिथी 1466 के बाद ले माने लें।<ref name="ignca" /> हालाँकि, मजुमदार अपना बिचार में 1460 के बाद बिद्यापति के होखे के बात के खंडन करे लें। मजुमदार के मोताबिक विद्यापति के जिनगी के महत्व वाला घटना सभ के क्रम बा: 1380 के आसपास इनके जनम, 1395-96 ईसवी के आसपास पद लिख के गियासुद्दीन आ नसरत शाह के समर्पित कइल, 1397 में सुलतान जौनपुर द्वारा तिरहुत जीतल गइल जेकरे पहिले ई दुनों पद लिखल गइल रहलें; 1400 के आसपास ''भूपरिक्रमा'' के रचना, 1402-04 के बीच इब्राहिम शाह द्वारा तिरहुत के सिंघासन पर कीर्तिसिंह के स्थापित कइल आ ओही समय के आसपास ''कीर्तिलता'' के रचना; 1410 से 1414 के बीच शिवसिंह के राज्यकाल में दू सौ पद सभ के रचना; 1440 से 1460 के बीच ''विभागसागर'', ''दान-वाक्यावली'', आ ''दुर्गाभक्ति तरंगिणी'' के रचना।{{sfn|शिवप्रसाद सिंह|2007|pp=53-54}} [[हिंदी साहित्य]] के परंपरा में, विद्यापति के समय "आदिकाल" में परे ला। आदिकाल के दूसर नाँव "वीरगाथा काल" भी हवे, हालाँकि, विद्यापति के काब्य के बीरगाथा से कवनो तालमेल ना बा, साथे-साथ ई एह काल के समाप्ति के बाद ले रहलें अइसन भी बिचार कइल जाला, यानी लोग इनके आदिकाल में रखे पर संतोख ना करे ला। आदिकाल के बाद के समय के "भक्तिकाल" मानल जाला, जबकि रामचंद्र शुक्ल इनका के भक्ति वाला कवी ना माने लें।{{sfn|रामचंद्र शुक्ल|2010|p=37}} शुक्ल जी के अइसन बिचार के कारन विद्यापति के शृंगार प्रधान रचना बा।{{efn|शुकुल जी के कहनाम कोट कइल जाला कि "विद्यापति के पद अधिकतर शृंगार के ही हैं जिनमें नायिका और नायक राधा-कृष्ण हैं। इन पदों की रचना जयदेव के गीतकाव्य के अनुकरण पर ही शायद की गई है। इनका माधुर्य अद्भूत है। विद्यापति शैव थे। इन्होंने इन पदों की रचना शृंगार काव्य की दृष्टि से की है, भक्त के रूप में नहीं। विद्यापति को कृष्णभक्तों की परंपरा में नहीं समझना चाहिये।"<ref name="dustudy">{{cite web|title=विद्यापति भक्त या शृंगारिक कवि|url=http://vle.du.ac.in/mod/book/print.php?id=12922&chapterid=27770|website=du.ac.in|publisher=दिल्ली विश्वविद्यालय|accessdate=5 मार्च 2018|language=hi}}{{Dead link|date=September 2023 |bot=InternetArchiveBot |fix-attempted=yes }}</ref>}} जबकि [[हजारी प्रसाद द्विवेदी]] शुकुल जी के बिचार के निवारण करे लें आ विद्यापति के भक्ती वाली रचना सभ के पूरबी भाषा सभ के साहित्य पर बाद में परल परभाव के ओर धियान दिवावे लें।{{efn|हजारी प्रसाद जी के हवाला दिहल जाला की, "विद्यापति शृंगार रस के सिद्धवाक कवि थे। उनकी पदावली में राधा और कृष्ण की जिस प्रेमलीला का चित्रण है, वह अपूर्व है। इस वर्णन में प्रेम के शरीर पक्ष की प्रधानता अवश्य है पर इससे सहृदय के चित्त में विकार नहीं उत्पन्न होता बल्कि भावों की सांद्रता और अभिव्यक्ति की प्रेषणगुणिता के कारण वह बहुत ही आकर्षक हो गया है। ...राधा और कृष्ण के प्रेम प्रसंगों को यह पुस्तक प्रथम बार उत्तर भारत में गेय पदों में प्रकाशित करती है। इस पुस्तक के पदों ने आगे चलकर बंगाल, असम, और उड़ीसा के वैष्णव भक्तों को खूब प्रभावित किया और उन प्रदेशों के भक्ति साहित्य में नई प्रेरणा और प्राणधारा संचारित करने में समर्थ हुई। इसीलिए पूर्वी प्रदेशों में सर्वत्र यह पुस्तक धर्म ग्रंथ की महिमा पा सकी।"<ref name="dustudy" />}} कुल मिला के विद्यापति, अपना काब्य के बिसेस्ता के आधार पर ना त आदिकाल के कवी के रूप में साबित होखे लें ना भक्ति काल के बिसेस्ता उनुका रचना में निर्बिबाद रूप से खोजल जा सके ला। दिनकर के कहनाम बा की, ''"...विद्यापति कवनो बर्ग में ना समा सके लें। उनुके सत्कार खाती अइसन सिंघासन चाही जवना प खाली उहे बइठ सके लें। ऊ खाली कबी रहलें आ कबिता में सौंदर्य आ आनंद के छाड़ के ऊ अउरी कवनो बात के जगहा ना दें।"''{{efn|''"विद्यापति किसी भी वर्ग में नहीं समा सकते। उनके सत्कार के लिए ऐसा सिंहासन चाहिए जिस पर केवल वही बैठ सकते हैं। वे केवल कवि थे और कविता में सौन्दर्य और आनन्द को छोड़ कर वे किसी और बात को स्थान नहीं देते थे।"'' रामधारी सिंह दिनकर।{{sfn|रामधारी सिंह दिनकर|2008|p=12}} }} == रचना संसार == [[चित्र:Statue of Maha Kavi Kokil Vidyapati.jpg|thumb|alt=सीना से ऊपर के हिस्सा देखावत एगो मूर्ती|विद्यापति के एगो मुर्ती, बिस्फी, [[मधुबनी]], बिहार]] === पदावली === पदावली बिद्यापति के सभसे परसिद्ध रचना हवे। एह में गावल जा सके वाला सुघर पद बाड़ें। इनहन के बिसय शिव के भक्ती, राधा-कृष्ण के प्रेम, आम प्रेम आ सुंदरता के बखान बाटे। === अन्य रचना === अउरी बिबिध बिसय पर बिद्यापति कलम चलवलें जेह में मुख्य-मुख्य बाड़ें: * ''पुरुष परीक्षा'' — मुख्य रूप से नैतिक शिक्षा आ सदाचार संबंधी उपदेशन पर आधारित बा। हाल में प्रकाशन विभाग, [[भारत सरकार]] द्वारा एह ग्रंथ के हिंदी अनुबाद प्रकाशित कइल गइल बा, जेकर अनुबाद अखिलेश झा कइले बाड़ें। एह पुस्तक में मूल रचना के 44 कहानी सभ में से चुनल गइल 25 कहानी शामिल बाड़ी स। एकरे साथे पुस्तक में विद्यापति आ पुरुष परीक्षा दुनो पर विद्वत्तापूर्ण भूमिका (परिचयात्मक लेख) भी दिहल गइल बा। पुरुष परीक्षा के प्रमुख विषय नैतिक शिक्षा, मानव चरित्र के परख आ आदर्श आचरण के मार्गदर्शन हवे। * ''लिखनावली'' लेखन के काम के बारे में। * ''भू-परिक्रमा'', भूगोल के बारे में, एह में आसपास के जगह वगैरह के बरनन बाटे। * ''विभागसार'' आत्मकथा नियर रचना। * ''दानवाक्यावली'' दान के बारे में। * ''गंगावाक्यावली'' * ''वर्षकृत्य'' * ''दुर्गाभक्तितरंगिणी'' * ''शैवसर्वस्वहार'' * ''कीर्तिपताका'' * ''कीर्तिलता'' {{clear}} ==नोट आ टीका-टिप्पणी== <references group="नोट"/> ==संदर्भ== {{Reflist|35em}} ==संदर्भ ग्रंथ== {{refbegin}} * {{cite book|author=रामचंद्र शुक्ल |title=हिंदी साहित्य का इतिहास|url=|year=2010|publisher=लोकभारती प्रकाशन |place=इलाहाबाद}}<!-- {{sfn|रामचंद्र शुक्ल|2010|p=37}} --> * {{cite book|author=रामधारी सिंह दिनकर|title=कवि और कविता|url=https://books.google.com/books?id=9FqZPDa6NqcC&pg=PA11|year=2008|publisher=राजकमल प्रकाशन |isbn=978-81-8031-324-0}}<!-- {{sfn|रामधारी सिंह दिनकर|2008|p=}} --> * {{cite book|author=शिवप्रसाद सिंह|title=कीर्तिलता और अवहट्ट भाषा |url=https://books.google.com/books?id=TVkvD3EZaBIC&pg=PT209|year=1999|publisher=वाणी प्रकाशन|isbn=978-93-5000-020-5}} <!-- {{sfn|शिवप्रसाद सिंह|1999|p=}} --> * {{cite book|author=शिवप्रसाद सिंह|title=विद्यापति |url=https://books.google.com/books?id=0eWvGQAn7NwC |year=2007 |publisher=लोकभारती प्रकाशन ( अब राजकमल प्रकाशन) |place=इलाहाबाद|isbn=}} <!-- {{sfn|शिवप्रसाद सिंह|2007|p=}} --> * {{cite book|author=शुभकार कपूर|title=विद्यापति और उनका काव्य: महाकवि विद्यापति की काव्य-कला एवं जीवनाकृति |url=https://books.google.com/books?id=y7VHAAAAMAAJ|year=1966|publisher=गंगा पुस्तकमाला कार्यालय}}<!-- {{sfn|शुभकार कपूर|1966|p=}} --> * {{cite book|author=मोहन लाल|title=Encyclopaedia of Indian Literature: Sasay to Zorgot|url=https://books.google.com/books?id=KnPoYxrRfc0C&pg=PA4565|year=1992|publisher=साहित्य अकादमी|isbn=978-81-260-1221-3}}<!-- {{sfn|मोहन लाल|1992|p=}} --> * {{cite book|author=राम दयाल राकेश|title=Vidyapati, the Greatest Poet of Mithila|url=https://books.google.com/books?id=MbBIAQAAIAAJ|year=2007|publisher=Greater Janakpur Area Development Council|isbn=978-9937-2-0148-3}}<!-- {{sfn|राम दयाल राकेश|2007|p=}} --> * {{cite book|author=विद्यापति ठाकुर|title=Vidyapati Bangiya Padabali: Songs of the Love of Radha and Krishna |trans-title=विद्यापति बंगीय पदाबली: राधा अउरी कृष्ण के परेम-गीत |url=https://books.google.com/books?id=OqVDRnEf6SEC&pg=PT3|year=1979|publisher=Library of Alexandria|isbn=978-1-4655-1475-2}}<!-- {{sfn|विद्यापति ठाकुर|1979|p=1}} --> * {{cite book|author=राधाकृष्ण चौधरी|title=A Survey of Maithili Literature |trans-title=मैथिली साहित्य के सर्वेक्षण |url=https://books.google.com/books?id=C0f898HDLAYC&pg=PA51|year=1976|publisher=Ram Vilas Sahu|isbn=978-93-80538-36-5}} <!-- {{harv|राधाकृष्ण चौधरी|1976|p=}} --> {{refend}} ==बाहरी कड़ी== * [http://www.kavitakosh.org/kk/index.php?title=%E0%A4%B5%E0%A4%BF%E0%A4%A6%E0%A5%8D%E0%A4%AF%E0%A4%BE%E0%A4%AA%E0%A4%A4%E0%A4%BF विद्यापति कऽ रचना सभ] {{Webarchive|url=https://web.archive.org/web/20090504122744/http://www.kavitakosh.org/kk/index.php?title=%E0%A4%B5%E0%A4%BF%E0%A4%A6%E0%A5%8D%E0%A4%AF%E0%A4%BE%E0%A4%AA%E0%A4%A4%E0%A4%BF |date=2009-05-04 }}, कविता कोश में। {{In lang|hi}} * [http://www.cse.iitk.ac.in/~amit/books/vidyapati-1963-love-songs-of.html 27 poems] transl. Deben Bhattacharya, from ''Love Songs of Vidyapati'', (UNESCO) 1963. {{in lang|en}} * [https://hdl.handle.net/2027/coo1.ark:/13960/t6446799m {{IAST|Songs of the love of Rādhā and Krishna}}], translated into English by Ananda Coomaraswamy and Arun Sen 1915. {{in lang|en}} <!-- https://www.prabhatkhabar.com/news/63307.aspx बादमें इस्तेमाल खाती --> <!-- http://www.pupdepartments.ac.in/de/lesson/ug/ba/Semester%203/Hindi/L-%201-15%20Title.pdf बाद में इस्तेमाल खाती --> <!-- http://www.sahityasudha.com/articles_nov_2016/lekh/nayana_deliwala/lekh_adikaleen_kaviyon.html बाद में इस्तेमाल खाती --> {{Authority control}} [[श्रेणी:भारतीय कवि]] [[श्रेणी:मैथिली-भाषा के लेखक]] [[श्रेणी:मैथिली-भाषा के कवि]] [[श्रेणी:बिहार के लोग]] [[श्रेणी:हिंदी-भाषा के कवि]] [[श्रेणी:15वीं-सदी के भारतीय कवि]] [[श्रेणी:मध्यजुग के भारतीय कवि]] dm4bicnjpfuaio0lzyxtr45koc5nhh2 रबींद्रनाथ टैगोर 0 8658 797601 796936 2026-06-09T08:04:41Z अजीत कुमार तिवारी 4590 हिज्जे/इस्पेलिंग सुधार 797601 wikitext text/x-wiki {{Infobox writer | name = रबींद्रनाथ ठाकुर | native_name = রবীন্দ্রনাথ ঠাকুর | image = Tagore3.jpg | image_size = 225 | alt = Late-middle-aged bearded man in white robes looks to the left with serene composure. | caption = रबींद्रनाथ ठाकुर (c.1915) | birth_name = Rabindranath Thakur | birth_date = {{Birth date|df=yes|1861|05|07}} | birth_place = [[कलकत्ता]], [[बंगाल प्रेसीडेंसी]], [[ब्रिटिश भारत]] | death_date = {{Death date and age|df=yes|1941|08|07|1861|05|07}} | death_place = कलकत्ता | occupation = कवि, लेखक, चित्रकार, संगीतकार | language = [[बंगाली भाषा|बंगाली]], अंग्रेजी | nationality = | education = | alma_mater = | period = | genre = | subject = | movement = | notableworks = ''[[गीतांजली]]'', ''गोरा'', ''[[घरे-बाहिरे]]'', ''[[जन गण मन]]'', ''[[रबीन्द्र संगीत]]'', ''[[आमार सोनार बांग्ला]]'' | influences = | spouse = {{marriage|मृणालिनी देवी|1883|1902}} | children = पाँच गो संतान, दू के बचपन में मउत हो गइल | relatives = | awards = {{awd|[[साहित्य के नोबल पुरस्कार]]|1913}}<!-- do not add image icons such as nobel peace, see [[:Template:Infobox writer]] --> | signature = Rabindranath Tagore Signature.svg | signature_alt = Close-up on a Bengali word handwritten with angular, jaunty letters. }} '''रबींद्रनाथ ठाकुर''' ({{Langx|bn|রবীন্দ্রনাথ ঠাকুর}}, रोबिंद्रोनाथ ठाकुर) (7 मई 1861 — 7 अगस्त 1941) के '''रबींद्रनाथ टैगोर''' आऊर '''गुरुदेव''' के नाम से भी जानल जाला। रबींद्रनाथ दुनिया क जानल मानल कवि, साहित्यकार, दार्शनिक आ भारतीय साहित्य क एकमात्र [[नोबल पुरस्कार]] बिजेता हउवन। ई [[एशिया]] के पहिला [[नोबेल पुरस्कार]] सम्मानित व्यक्ति बाटें। ई एकलौता कवि बाटें जिनकर दू रचना दू गो देशन क राष्ट्रगान बा — [[भारत]] क राष्ट्रगान ''[[जन गण मन]]'' आ [[बाँग्लादेश]] क राष्ट्रीय गान ''आमार सोनार बाँग्ला'' ठाकुर क ही रचना बाड़ीं। {{clear}} == बाहरी कड़ी == * [http://www.nobel.se/literature/laureates/1913/tagore-bio.html रबींद्रनाथ, जीवनी, Nobel Foundation] * [http://www.nobel.se/literature/laureates/1913/press.html Nobel Prize in Literature Presentation Speech from the official website of Nobel Foundation] {{Authority control}} [[श्रेणी:1961 में जनम]] [[श्रेणी:1941 में निधन]] [[श्रेणी:नोबेल पुरस्कार विजेता]] [[श्रेणी:साहित्यकार]] [[श्रेणी:बांग्ला साहित्यकार]] [[श्रेणी:नोबेल पुरस्कार सम्मानित भारतीय]] [[श्रेणी:लेखक]] [[श्रेणी:भारत के लोग]] [[श्रेणी:अंग्रेजी-भाषा के भारतीय कवि]] {{poet-stub}} dfi6ti5ofyjt70w2ducp2fyw8hopfdl वार्तालाप:रबींद्रनाथ टैगोर 1 8983 797600 673037 2026-06-09T07:59:52Z अजीत कुमार तिवारी 4590 /* नाँव बदलाव अनुरोध 9 जून 2026 */ नया खंड 797600 wikitext text/x-wiki {{वार्ता शीर्षक}} {{WikiProject Biography | living= no | class= Stub | authorbio-work-group = yes }} == नाँव बदलाव अनुरोध 9 जून 2026 == {{requested move/dated|रबींद्रनाथ ठाकुर}} [[:रबींद्रनाथ टैगोर]] → {{no redirect|रबींद्रनाथ ठाकुर}} – मूल बांग्ला में ठाकुर कहल जाला। [[User:अजीत कुमार तिवारी|<span style="text-shadow:gray 3px 3px 2px;color:red">'''अजीत कुमार तिवारी'''</span>]]<sup>[[User talk:अजीत कुमार तिवारी|<span style="color:green"> '''बातचीत'''</span>]]</sup> 07:59, 9 जून 2026 (UTC) r3l0bp5tfkd1vbnzy70pdwd417g6p1d कबीर 0 9246 797602 790505 2026-06-09T08:54:34Z SM7 3953 बिना प्रमाणिक स्रोत संदर्भ 797602 wikitext text/x-wiki [[Image:Kabir004.jpg|thumbnail|An 1825 CE painting depicts Kabir weaving|right]] '''कबीरदास''' चाहे संत '''कबीर'''<ref>{{cite book|author=Jaroslav Strnad|title=Morphology and Syntax of Old Hindī: Edition and Analysis of One Hundred Kabīr vānī Poems from Rājasthān|url=https://books.google.com/books?id=clUCLcIKXO4C&pg=PA10 |year=2013|publisher=BRILL Academic|isbn=978-90-04-25489-3|page=10}}</ref> 15वीं सदी के एगो संत महात्मा, रहस्यवादी आ कवी जिनके रचना आ उपदेश के परभाव, कुछ बिद्वान लोग के अनुसार तत्कालीन भक्ति आंदोलन पर पड़ल। इनकर जनम [[बनारस]] की लगे लहरतारा में भइल रहे। धार्मिक पाखण्ड की खिलाफ उपदेश दिहलें आ भगवान की निर्गुण रूप के आराधना करे के उपदेश दिहलें। निर्गुण भक्ति की [[कवि]] लोगन में कबीरदास क अस्थान बहुत ऊपर बा। कबीर के रचना सभ के अंश [[सिख धर्म]] के पबित्र ग्रंथ [[गुरु ग्रंथ साहिब]] में भी शामिल मिले ला।<ref name=britannicakabir>[http://www.britannica.com/EBchecked/topic/309270/Kabir Kabir] Encyclopædia Britannica (2015)Accessed: 27 जुलाई 2015</ref><ref name="Tinker1990">{{cite book|author=Hugh Tinker|title=South Asia: A Short History|url=https://books.google.com/books?id=n5uU2UteUpEC&pg=PA76|accessdate=12 जुलाई 2012|year=1990|publisher=University of Hawaii Press|isbn=978-0-8248-1287-4|pages=75–77}}</ref> उ जुलाहा के काम करके निर्वाह करत रहले। कबीर साहेब जी के प्रकट धरती पर भारत वर्ष के पावन भूमि काशी में भईल रहे। कबीर सागर के अनुसार उहा के सशरीर प्रकट सन 1398 (संवत 1455), में ज्येष्ठ मास की पूर्णिमा के ब्रह्ममूहर्त के समय लहरतारा तालाब में कमल के फूल पर भईल रहे । जहां से नीरू नीमा नामक दंपति उठा के ले गइल  रहले। उनके इ लीला के उनके अनुयायी कबीर साहेब प्रकट दिवस के रूप में मनावेलालो। बाद में ई हिंदू संत आ गुरू रामानंद के परभाव में अइलें आ उनके चेला बन गइलें।<ref>{{Cite web|url=https://www.britannica.com/biography/Kabir-Indian-mystic-and-poet|title=Kabir|last=Encyclopedia Brittanica|first=The Editors of|date=1 जनवरी 2019|website=Encyclopedia Brittanica|accessdate=20 जनवरी 2019}}</ref> कबीर के हिंदू आ [[इस्लाम|मुस्लिम]] दुनों धरम के आलोचना करे खाती जानल जाला। उनके अनुसार हिंदू लोग वेद से आ करमकांड से भरमाव में बा आ ऊ एह लोग के धार्मिक रेवाज सभ के भरपूर आलोचना कइलें जेह में जनेव आ खतना दुनों के बिरोध सामिल बा।<ref name="GarciaHenderson2002">{{cite book|author1=Carol Henderson Garcia|author2=Carol E. Henderson|title=Culture and Customs of India|url=https://books.google.com/books?id=CaRVePXX6vEC&pg=PA70|accessdate=12 जुलाई 2012|year=2002|publisher=Greenwood Publishing Group|isbn=978-0-313-30513-9|pages=70–71}}</ref> इनका जिनगी में हिंदू आ मुस्लिम दुनों धरम के लोग इनके बिचार खातिर इनका के धमकावल। निधन के बाद इनके ऊपर दुनों धरम के लोग दावा कइल।<ref name="Tinker1990"/>(एह बात पर बिबाद भइल कि इनके जरावल जाय कि दफन कइल जाव)। == कृतियां == धर्मदास उनके वाणी क संग्रह " बीजक " नाम के ग्रंथ मे कइन जेकर तीन मुख्य भाग ह: साखी , सबद (पद), रमैनी *''' साखी''': संस्कृत ' साक्षी , शब्द क बिगड़ल रूप ह आ धर्मोपदेश के अर्थ में प्रयोग भयल ह। अधिक साखियां दोहे में लिखल गयल ह पर ओम्में सोरठा क भी प्रयोग मिलल ह। कबीर क शिक्षा और सिद्धांत क निरूपण अधिकतर साखी में भयल ह। *'''सबद ''' गेय पद ह जेम्मा पूरी तरह संगीतात्मकता विद्यमान ह। एम्में उपदेशात्मकता के स्थान पर भावावेश क प्रधानता ह; काहें से कि एम्में कबीर के प्रेम आ अंतरंग साधना क अभिव्यक्ति भइल ह। *'''रमैनी ''' चौपाई छंद में लिखल गयल ह एम्में कबीर के रहस्यवादी आ दार्शनिक विचारन के बतावल गयल ह। [[File:Kabir-stamp-370x630.jpg|thumb|Kabir-stamp-370x630]] {{clear}} ==संदर्भ== {{Reflist|33em}} [[श्रेणी:भक्ति आंदोलन]] [[श्रेणी:हिंदी-भाषा के कवि]] [[श्रेणी:बनारस के लोग]] [[श्रेणी:बनारस जिला के लोग]] {{कवि-आधार}} q2xj9d4677ddyrekko2t0ywgc69tclr अर्थशास्त्र 0 15078 797558 780327 2026-06-08T21:06:11Z SM7 3953 सुधार कइल गइल 797558 wikitext text/x-wiki {{for|कौटिल्य द्वारा रचित ग्रंथ|अर्थशास्त्रम्}} [[चित्र:Supply-demand-right-shift-demand.svg|240px|thumb|alt=डिमांड आ सप्लाई ग्राफ| सप्लाई आ डिमांड के डाइग्राम; मांग में बढ़ती के परभाव देखा रहल बा।]] '''अर्थशास्त्र''' ({{Lang|en|Economics}}; ''इकोनॉमिक्स'') [[सामाजिक विज्ञान]] के साखा हऽ जेकरा भीतर चीजन आ सेवा कुल के उत्पादन, वितरन, बिनिमय आ उपभोग के अधयन करल जाएला। 'अर्थशास्त्र' सबद [[संस्कृत]] सबद अर्थ (धन) आ शास्त्र के जोड़ से बनल बा, जेकर ठेत अर्थ होखी - 'धन के अधयन'। कवनो बिषय से जुड़ला में मुनष्यन के काजन के ज्ञान के उ बिषय के शास्त्र कहल जाएला, इहे खातिर अर्थशास्त्र में इंसान के अरथ से जुड़ल काजन के ज्ञान होखल जरूरी बा। अर्थशास्त्र के फोकस एह बात पर होला कि ढेर तरीका के[[एजेंट (अर्थशास्त्र)|आर्थिक एजेंट]] सभ के बेहवार आ आपसी तालमेल कवना तरीका के बा आ [[अर्थब्यवस्था|अरथ बेवसथा]] सभ कइसे काम करे लीं। [[माइक्रोइकोनॉमिक्स]] में अर्थब्यवस्था के बेसिक तत्व सभ के बिस्लेषण कइल जाला जेह में एकहन एजेंट आ बजार (मार्केट), इनहन के अंतर्क्रिया, आ एह अंतर्क्रिया (इंटरैक्शन) सभ के परिणाम के अध्ययन शामिल होला। एह एकहक ठो एजेंट सभ में परिवार, फर्म, बिक्रेता, खरीदार वगैरह लोग सामिल होला। एकरे बिपरीत [[मैक्रोइकोनॉमिक्स]] में पूरा अर्थब्यवस्था के बिस्लेषण कइल जाला (मने कि पूरा संपूर्ण उत्पादन, उपभोग, बचत, आ निवेश) आ पूरा अर्थब्यवस्था के परभावित करे वाला मुद्दा, [[बेरोजगारी]], बिबिध किसिम के आर्थिक नीति वगैरह के अध्ययन आ बिस्लेषण कइल जाला। अर्थशास्त्रीय विवेचना के प्रयोग समाज से संबंधित विभिन्न क्षेत्रन में कइल जायेला, जैसे:- अपराध, शिक्षा, परिवार, स्वास्थ्य, कानून, राजनीति, धर्म, सामाजिक संस्थान और युद्ध इत्यदि। {{Clear}} ==संदर्भ== {{Reflist}} {{Economics}} {{Authority control}} [[श्रेणी:अर्थशास्त्र]] [[श्रेणी:सामाजिक बिज्ञान]] [[श्रेणी:एकैडमिक बिसय]] {{बिज्ञान-आधार}} 5270ppzievx2o05r5lk3jxnvj6w9fam 797559 797558 2026-06-08T22:29:50Z SM7 3953 सुधार कइल गइल 797559 wikitext text/x-wiki {{for|कौटिल्य द्वारा रचित ग्रंथ|अर्थशास्त्रम्}} [[चित्र:Supply-demand-right-shift-demand.svg|240px|thumb|alt=डिमांड आ सप्लाई ग्राफ| सप्लाई आ डिमांड के डाइग्राम; मांग में बढ़ती के परभाव देखा रहल बा।]] '''अर्थशास्त्र''' ({{Lang|en|Economics}}; ''इकोनॉमिक्स'') [[सामाजिक विज्ञान]] के साखा हऽ जेकरा भीतर बस्तु आ सेवा (गुड्स एंड सर्विसेस) कुल के उत्पादन, बितरन, बिनिमय आ उपभोग के अधयन करल जाएला। 'अर्थशास्त्र' शब्द [[संस्कृत]] के 'अर्थ' (माने [[धन]]) आ 'शास्त्र' के जोड़ से बनल बा, जेकर ठेत अर्थ होखी - 'धन के अधयन'। कवनो बिषय से जुड़ला में मुनष्यन के काजन के ज्ञान के उ बिषय के शास्त्र कहल जाएला, इहे खातिर अर्थशास्त्र में इंसान के अरथ से जुड़ल काजन के ज्ञान होखल जरूरी बा। अर्थशास्त्र के फोकस एह बात पर होला कि ढेर तरीका के[[एजेंट (अर्थशास्त्र)|आर्थिक एजेंट]] सभ के बेहवार आ आपसी तालमेल कवना तरीका के बा आ [[अर्थब्यवस्था|अरथ बेवसथा]] सभ कइसे काम करे लीं। [[माइक्रोइकोनॉमिक्स]] में अर्थब्यवस्था के बेसिक तत्व सभ के बिस्लेषण कइल जाला जेह में एकहन एजेंट आ बजार (मार्केट), इनहन के अंतर्क्रिया, आ एह अंतर्क्रिया (इंटरैक्शन) सभ के परिणाम के अध्ययन शामिल होला। एह एकहक ठो एजेंट सभ में परिवार, फर्म, बिक्रेता, खरीदार वगैरह लोग सामिल होला। एकरे बिपरीत [[मैक्रोइकोनॉमिक्स]] में पूरा अर्थब्यवस्था के बिस्लेषण कइल जाला (मने कि पूरा संपूर्ण उत्पादन, उपभोग, बचत, आ निवेश) आ पूरा अर्थब्यवस्था के परभावित करे वाला मुद्दा, [[बेरोजगारी]], बिबिध किसिम के आर्थिक नीति वगैरह के अध्ययन आ बिस्लेषण कइल जाला। अर्थशास्त्रीय विवेचना के प्रयोग समाज से संबंधित विभिन्न क्षेत्रन में कइल जायेला, जैसे:- अपराध, शिक्षा, परिवार, स्वास्थ्य, कानून, राजनीति, धर्म, सामाजिक संस्थान और युद्ध इत्यदि। {{Clear}} ==संदर्भ== {{Reflist}} {{Economics}} {{Authority control}} [[श्रेणी:अर्थशास्त्र]] [[श्रेणी:सामाजिक बिज्ञान]] [[श्रेणी:एकैडमिक बिसय]] {{बिज्ञान-आधार}} i88am4h8tl5rv1y3rz07fcaank3inf9 797560 797559 2026-06-08T22:47:30Z SM7 3953 बिस्तार कइल गइल 797560 wikitext text/x-wiki {{for|कौटिल्य द्वारा रचित ग्रंथ|अर्थशास्त्रम्}} [[चित्र:Supply-demand-right-shift-demand.svg|240px|thumb|alt=डिमांड आ सप्लाई ग्राफ| सप्लाई आ डिमांड के डाइग्राम; मांग में बढ़ती के परभाव देखा रहल बा।]] '''अर्थशास्त्र''' ({{Lang|en|Economics}}; ''इकोनॉमिक्स'') [[सामाजिक विज्ञान]] के साखा हऽ जेकरा भीतर बस्तु आ सेवा (गुड्स एंड सर्विसेस) कुल के उत्पादन, बितरन, बिनिमय आ उपभोग के अधयन करल जाएला। 'अर्थशास्त्र' शब्द [[संस्कृत]] के 'अर्थ' (माने [[धन]]) आ 'शास्त्र' के जोड़ से बनल बा, जेकर ठेत अर्थ होखी - 'धन के अधयन'। कवनो बिषय से जुड़ला में मुनष्यन के काजन के ज्ञान के उ बिषय के शास्त्र कहल जाएला, इहे खातिर अर्थशास्त्र में इंसान के अरथ से जुड़ल काजन के ज्ञान होखल जरूरी बा। अर्थशास्त्र के फोकस एह बात पर होला कि ढेर तरीका के[[एजेंट (अर्थशास्त्र)|आर्थिक एजेंट]] सभ के बेहवार आ आपसी तालमेल कवना तरीका के बा आ [[अर्थब्यवस्था|अरथ बेवसथा]] सभ कइसे काम करे लीं। [[माइक्रोइकोनॉमिक्स]] में अर्थब्यवस्था के बेसिक तत्व सभ के बिस्लेषण कइल जाला जेह में एकहन एजेंट आ बजार (मार्केट), इनहन के अंतर्क्रिया, आ एह अंतर्क्रिया (इंटरैक्शन) सभ के परिणाम के अध्ययन शामिल होला। एह एकहक ठो एजेंट सभ में परिवार, फर्म, बिक्रेता, खरीदार वगैरह लोग सामिल होला। एकरे बिपरीत [[मैक्रोइकोनॉमिक्स]] में पूरा अर्थब्यवस्था के बिस्लेषण कइल जाला (मने कि पूरा संपूर्ण उत्पादन, उपभोग, बचत, आ निवेश) आ पूरा अर्थब्यवस्था के परभावित करे वाला मुद्दा, [[बेरोजगारी]], बिबिध किसिम के आर्थिक नीति वगैरह के अध्ययन आ बिस्लेषण कइल जाला। अर्थशास्त्रीय विवेचना के प्रयोग समाज से संबंधित विभिन्न क्षेत्रन में कइल जायेला, जैसे:- अपराध, शिक्षा, परिवार, स्वास्थ्य, कानून, राजनीति, धर्म, सामाजिक संस्थान और युद्ध इत्यदि। == माइक्रो इकोनॉमिक्स == {{main|माइक्रोइकोनॉमिक्स}} माइक्रोइकोनॉमिक्स ({{Langx|en|व्यष्टि अर्थशास्त्र}}) एह बात के अध्ययन करे ला कि बजार ढाँचा के हिस्सा बने वाली अलग-अलग इकाई (एंटिटी) बजार के भीतर एक-दूसरा के साथ कइसे संपर्क करे लीं आ आपस में मिल के केङ्ने बजार बेवस्था के निर्माण करे लीं। एह इकाई सभ में निजी आ सार्वजनिक दुनो तरह के भागीदार शामिल हो सकेलें। ई सभ आमतौर पर सीमित संसाधन (स्कार्सिटी) आ नियमन (रेगुलेशन) के परिस्थिति में काम करेलन। बजार में खरीद-बिक्री होखे वाला वस्तु कवनो ठोस उत्पाद, जइसे सेब, भा कवनो सेवा, जइसे मरम्मत सेवा, कानूनी सलाह भा मनोरंजन सेवा, हो सकेला। अर्थशास्त्र में कई तरह के बाजार संरचना पावल जाली। [[परफेक्ट कंपटीशन]] (पूर्ण प्रतियोगिता) वाला बजार में कवनो भागीदार एतना बड़हन ना होला कि ऊ अकेले कवनो समान प्रकार के उत्पाद के दाम तय कर सके। एह स्थिति में सभे भागीदार प्राइस टेकर (दाम स्वीकार करे वाला) होलें, काहे कि कवनो सिंगल बिक्रेता प्रोडक्ट के दाम पर प्रभाव ना डाल सके ला। हालाँकि, असल दुनिया में अधिकतर बजार परफेक्ट कंपटीशन वाला ना होके इम्परफेक्ट कंपटीशन वाला होलें। इम्परफेक्ट कंपटीशन के कई रूप बाड़ें। एकाधिकार (मोनोपोली) में कवनो वस्तु के खाली एक्के ठो बिक्रेता होला। द्वयाधिकार (डुओपोली) में दू गो बिक्रेता होलें, जबकि ओलिगोपोली में बिक्रेता लोगन के गिनती कम होला। मोनोपोलिस्टिक कंपटीशन में बहुत बिक्रेता होखे लें, बाकी ऊ सभ अलग-अलग बिसेस्ता वाला प्रोडक्ट बेचेलन। मोनोपसनी में खाली एके गो खरीदार होला, जबकि ओलिगोपसनी में खरीददारन के संख्या कम रहेला। इम्परफेक्ट कंपटीशन वाला बाजार में काम करे वाली फर्म सभ के अक्सर प्राइस मेकर (दाम निश्चित करे वाला) बने के क्षमता होले। मतलब, ऊ अपना उत्पाद के दाम पर कुछ हद तक प्रभाव डाल सकेली आ ओकरा के निर्धारित करे में भूमिका निभा सके लीं। {{Clear}} ==संदर्भ== {{Reflist}} {{Economics}} {{Authority control}} [[श्रेणी:अर्थशास्त्र]] [[श्रेणी:सामाजिक बिज्ञान]] [[श्रेणी:एकैडमिक बिसय]] {{बिज्ञान-आधार}} bkhu4gq3cd1qoq08xwfa4re29126lwm 797561 797560 2026-06-08T22:52:21Z SM7 3953 बिस्तार कइल गइल 797561 wikitext text/x-wiki {{for|कौटिल्य द्वारा रचित ग्रंथ|अर्थशास्त्रम्}} [[चित्र:Supply-demand-right-shift-demand.svg|240px|thumb|alt=डिमांड आ सप्लाई ग्राफ| सप्लाई आ डिमांड के डाइग्राम; मांग में बढ़ती के परभाव देखा रहल बा।]] '''अर्थशास्त्र''' ({{Lang|en|Economics}}; ''इकोनॉमिक्स'') [[सामाजिक विज्ञान]] के साखा हऽ जेकरा भीतर बस्तु आ सेवा (गुड्स एंड सर्विसेस) कुल के उत्पादन, बितरन, बिनिमय आ उपभोग के अधयन करल जाएला। 'अर्थशास्त्र' शब्द [[संस्कृत]] के 'अर्थ' (माने [[धन]]) आ 'शास्त्र' के जोड़ से बनल बा, जेकर ठेत अर्थ होखी - 'धन के अधयन'। कवनो बिषय से जुड़ला में मुनष्यन के काजन के ज्ञान के उ बिषय के शास्त्र कहल जाएला, इहे खातिर अर्थशास्त्र में इंसान के अरथ से जुड़ल काजन के ज्ञान होखल जरूरी बा। अर्थशास्त्र के फोकस एह बात पर होला कि ढेर तरीका के[[एजेंट (अर्थशास्त्र)|आर्थिक एजेंट]] सभ के बेहवार आ आपसी तालमेल कवना तरीका के बा आ [[अर्थब्यवस्था|अरथ बेवसथा]] सभ कइसे काम करे लीं। [[माइक्रोइकोनॉमिक्स]] में अर्थब्यवस्था के बेसिक तत्व सभ के बिस्लेषण कइल जाला जेह में एकहन एजेंट आ बजार (मार्केट), इनहन के अंतर्क्रिया, आ एह अंतर्क्रिया (इंटरैक्शन) सभ के परिणाम के अध्ययन शामिल होला। एह एकहक ठो एजेंट सभ में परिवार, फर्म, बिक्रेता, खरीदार वगैरह लोग सामिल होला। एकरे बिपरीत [[मैक्रोइकोनॉमिक्स]] में पूरा अर्थब्यवस्था के बिस्लेषण कइल जाला (मने कि पूरा संपूर्ण उत्पादन, उपभोग, बचत, आ निवेश) आ पूरा अर्थब्यवस्था के परभावित करे वाला मुद्दा, [[बेरोजगारी]], बिबिध किसिम के आर्थिक नीति वगैरह के अध्ययन आ बिस्लेषण कइल जाला। अर्थशास्त्रीय विवेचना के प्रयोग समाज से संबंधित विभिन्न क्षेत्रन में कइल जायेला, जैसे:- अपराध, शिक्षा, परिवार, स्वास्थ्य, कानून, राजनीति, धर्म, सामाजिक संस्थान और युद्ध इत्यदि। == माइक्रो इकोनॉमिक्स == {{main|माइक्रोइकोनॉमिक्स}} माइक्रोइकोनॉमिक्स ({{Langx|en|व्यष्टि अर्थशास्त्र}}) एह बात के अध्ययन करे ला कि बजार ढाँचा के हिस्सा बने वाली अलग-अलग इकाई (एंटिटी) बजार के भीतर एक-दूसरा के साथ कइसे संपर्क करे लीं आ आपस में मिल के केङ्ने बजार बेवस्था के निर्माण करे लीं। एह इकाई सभ में निजी आ सार्वजनिक दुनो तरह के भागीदार शामिल हो सकेलें। ई सभ आमतौर पर सीमित संसाधन (स्कार्सिटी) आ नियमन (रेगुलेशन) के परिस्थिति में काम करेलन। बजार में खरीद-बिक्री होखे वाला वस्तु कवनो ठोस उत्पाद, जइसे सेब, भा कवनो सेवा, जइसे मरम्मत सेवा, कानूनी सलाह भा मनोरंजन सेवा, हो सकेला। अर्थशास्त्र में कई तरह के बाजार संरचना पावल जाली। [[परफेक्ट कंपटीशन]] (पूर्ण प्रतियोगिता) वाला बजार में कवनो भागीदार एतना बड़हन ना होला कि ऊ अकेले कवनो समान प्रकार के उत्पाद के दाम तय कर सके। एह स्थिति में सभे भागीदार प्राइस टेकर (दाम स्वीकार करे वाला) होलें, काहे कि कवनो सिंगल बिक्रेता प्रोडक्ट के दाम पर प्रभाव ना डाल सके ला। हालाँकि, असल दुनिया में अधिकतर बजार परफेक्ट कंपटीशन वाला ना होके इम्परफेक्ट कंपटीशन वाला होलें। इम्परफेक्ट कंपटीशन के कई रूप बाड़ें। एकाधिकार (मोनोपोली) में कवनो वस्तु के खाली एक्के ठो बिक्रेता होला। द्वयाधिकार (डुओपोली) में दू गो बिक्रेता होलें, जबकि ओलिगोपोली में बिक्रेता लोगन के गिनती कम होला। मोनोपोलिस्टिक कंपटीशन में बहुत बिक्रेता होखे लें, बाकी ऊ सभ अलग-अलग बिसेस्ता वाला प्रोडक्ट बेचेलन। मोनोपसनी में खाली एके गो खरीदार होला, जबकि ओलिगोपसनी में खरीददारन के संख्या कम रहेला। इम्परफेक्ट कंपटीशन वाला बाजार में काम करे वाली फर्म सभ के अक्सर प्राइस मेकर (दाम निश्चित करे वाला) बने के क्षमता होले। मतलब, ऊ अपना उत्पाद के दाम पर कुछ हद तक प्रभाव डाल सकेली आ ओकरा के निर्धारित करे में भूमिका निभा सके लीं। पार्शियल इक्विलिब्रियम (आंशिक संतुलन) विश्लेषण सिस्टम में ई मान के चलल जाला कि जवन बजार के अध्ययन कइल जा रहल बा, ओकर गतिविधि के दुसरे बजारन पर कवनो प्रभाव नइखे पड़त। एह तरीका में एनालिसिस खाली एकही बजार तक सीमित रहे ला आ ओही बाजार के कुल गतिविधियन के आधार पर निष्कर्ष निकालल जाला।एकरा विपरीत, जनरल इक्विलिब्रियम थ्योरी एक्के संघे कई बजारन आ उनहन के बेहवार के अध्ययन करेला। ई तरीका सगरी बजारन के कुल गतिविधियन के एकजुट रूप में देखे ला आ एह बात के जाँच करेला कि अलग-अलग बजारन में होखे वाला बदलाव एक-दूसरा पर कइसे प्रभाव डाले लें। एह किसिम के एनालिसिस में बजारन के आपसी संबंध आ परस्पर क्रिया (इंतारैक्शन) के अध्ययन कइल जाला, जवना के परिणामस्वरूप अर्थव्यवस्था में संतुलन (इक्विलिब्रियम) स्थापित होखे ला। {{Clear}} ==संदर्भ== {{Reflist}} {{Economics}} {{Authority control}} [[श्रेणी:अर्थशास्त्र]] [[श्रेणी:सामाजिक बिज्ञान]] [[श्रेणी:एकैडमिक बिसय]] {{बिज्ञान-आधार}} llbqb1ow0elt7n0lxuifsygwa2enhp8 797562 797561 2026-06-08T23:17:47Z SM7 3953 बिस्तार कइल गइल 797562 wikitext text/x-wiki {{for|कौटिल्य द्वारा रचित ग्रंथ|अर्थशास्त्रम्}} [[चित्र:Supply-demand-right-shift-demand.svg|240px|thumb|alt=डिमांड आ सप्लाई ग्राफ| सप्लाई आ डिमांड के डाइग्राम; मांग में बढ़ती के परभाव देखा रहल बा।]] '''अर्थशास्त्र''' ({{Lang|en|Economics}}; ''इकोनॉमिक्स'') [[सामाजिक विज्ञान]] के साखा हऽ जेकरा भीतर बस्तु आ सेवा (गुड्स एंड सर्विसेस) कुल के उत्पादन, बितरन, बिनिमय आ उपभोग के अधयन करल जाएला। 'अर्थशास्त्र' शब्द [[संस्कृत]] के 'अर्थ' (माने [[धन]]) आ 'शास्त्र' के जोड़ से बनल बा, जेकर ठेत अर्थ होखी - 'धन के अधयन'। कवनो बिषय से जुड़ला में मुनष्यन के काजन के ज्ञान के उ बिषय के शास्त्र कहल जाएला, इहे खातिर अर्थशास्त्र में इंसान के अरथ से जुड़ल काजन के ज्ञान होखल जरूरी बा। अर्थशास्त्र के फोकस एह बात पर होला कि ढेर तरीका के[[एजेंट (अर्थशास्त्र)|आर्थिक एजेंट]] सभ के बेहवार आ आपसी तालमेल कवना तरीका के बा आ [[अर्थब्यवस्था|अरथ बेवसथा]] सभ कइसे काम करे लीं। [[माइक्रोइकोनॉमिक्स]] में अर्थब्यवस्था के बेसिक तत्व सभ के बिस्लेषण कइल जाला जेह में एकहन एजेंट आ बजार (मार्केट), इनहन के अंतर्क्रिया, आ एह अंतर्क्रिया (इंटरैक्शन) सभ के परिणाम के अध्ययन शामिल होला। एह एकहक ठो एजेंट सभ में परिवार, फर्म, बिक्रेता, खरीदार वगैरह लोग सामिल होला। एकरे बिपरीत [[मैक्रोइकोनॉमिक्स]] में पूरा अर्थब्यवस्था के बिस्लेषण कइल जाला (मने कि पूरा संपूर्ण उत्पादन, उपभोग, बचत, आ निवेश) आ पूरा अर्थब्यवस्था के परभावित करे वाला मुद्दा, [[बेरोजगारी]], बिबिध किसिम के आर्थिक नीति वगैरह के अध्ययन आ बिस्लेषण कइल जाला। अर्थशास्त्रीय विवेचना के प्रयोग समाज से संबंधित विभिन्न क्षेत्रन में कइल जायेला, जैसे:- अपराध, शिक्षा, परिवार, स्वास्थ्य, कानून, राजनीति, धर्म, सामाजिक संस्थान और युद्ध इत्यदि। == माइक्रो इकोनॉमिक्स == {{main|माइक्रोइकोनॉमिक्स}} माइक्रोइकोनॉमिक्स ({{Langx|hi|व्यष्टि अर्थशास्त्र}}) एह बात के अध्ययन करे ला कि बजार ढाँचा के हिस्सा बने वाली अलग-अलग इकाई (एंटिटी) बजार के भीतर एक-दूसरा के साथ कइसे संपर्क करे लीं आ आपस में मिल के केङ्ने बजार बेवस्था के निर्माण करे लीं। एह इकाई सभ में निजी आ सार्वजनिक दुनो तरह के भागीदार शामिल हो सकेलें। ई सभ आमतौर पर सीमित संसाधन (स्कार्सिटी) आ नियमन (रेगुलेशन) के परिस्थिति में काम करेलन। बजार में खरीद-बिक्री होखे वाला वस्तु कवनो ठोस उत्पाद, जइसे सेब, भा कवनो सेवा, जइसे मरम्मत सेवा, कानूनी सलाह भा मनोरंजन सेवा, हो सकेला। अर्थशास्त्र में कई तरह के बाजार संरचना पावल जाली। [[परफेक्ट कंपटीशन]] (पूर्ण प्रतियोगिता) वाला बजार में कवनो भागीदार एतना बड़हन ना होला कि ऊ अकेले कवनो समान प्रकार के उत्पाद के दाम तय कर सके। एह स्थिति में सभे भागीदार प्राइस टेकर (दाम स्वीकार करे वाला) होलें, काहे कि कवनो सिंगल बिक्रेता प्रोडक्ट के दाम पर प्रभाव ना डाल सके ला। हालाँकि, असल दुनिया में अधिकतर बजार परफेक्ट कंपटीशन वाला ना होके इम्परफेक्ट कंपटीशन वाला होलें। इम्परफेक्ट कंपटीशन के कई रूप बाड़ें। एकाधिकार (मोनोपोली) में कवनो वस्तु के खाली एक्के ठो बिक्रेता होला। द्वयाधिकार (डुओपोली) में दू गो बिक्रेता होलें, जबकि ओलिगोपोली में बिक्रेता लोगन के गिनती कम होला। मोनोपोलिस्टिक कंपटीशन में बहुत बिक्रेता होखे लें, बाकी ऊ सभ अलग-अलग बिसेस्ता वाला प्रोडक्ट बेचेलन। मोनोपसनी में खाली एके गो खरीदार होला, जबकि ओलिगोपसनी में खरीददारन के संख्या कम रहेला। इम्परफेक्ट कंपटीशन वाला बाजार में काम करे वाली फर्म सभ के अक्सर प्राइस मेकर (दाम निश्चित करे वाला) बने के क्षमता होले। मतलब, ऊ अपना उत्पाद के दाम पर कुछ हद तक प्रभाव डाल सकेली आ ओकरा के निर्धारित करे में भूमिका निभा सके लीं। पार्शियल इक्विलिब्रियम (आंशिक संतुलन) विश्लेषण सिस्टम में ई मान के चलल जाला कि जवन बजार के अध्ययन कइल जा रहल बा, ओकर गतिविधि के दुसरे बजारन पर कवनो प्रभाव नइखे पड़त। एह तरीका में एनालिसिस खाली एकही बजार तक सीमित रहे ला आ ओही बाजार के कुल गतिविधियन के आधार पर निष्कर्ष निकालल जाला।एकरा विपरीत, जनरल इक्विलिब्रियम थ्योरी एक्के संघे कई बजारन आ उनहन के बेहवार के अध्ययन करेला। ई तरीका सगरी बजारन के कुल गतिविधियन के एकजुट रूप में देखे ला आ एह बात के जाँच करेला कि अलग-अलग बजारन में होखे वाला बदलाव एक-दूसरा पर कइसे प्रभाव डाले लें। एह किसिम के एनालिसिस में बजारन के आपसी संबंध आ परस्पर क्रिया (इंतारैक्शन) के अध्ययन कइल जाला, जवना के परिणामस्वरूप अर्थव्यवस्था में संतुलन (इक्विलिब्रियम) स्थापित होखे ला। == मैक्रोइकोनॉमिक्स == मैक्रोइकोनॉमिक्स ({{Langx|hi|समष्टि अर्थशास्त्र}}) अर्थशास्त्र के एगो दूसर प्रमुख शाखा हवे, जवन पूरा [[अर्थव्यवस्था]] के समग्र रूप में अध्ययन करे ला। ई व्यापक आर्थिक सूचकन आ उनहन के परस्पर संबंधन के ऊपर से नीचे (टॉप-डाउन) दृष्टिकोण से समझे-समझावे के कोसिस करे ला, आ आमतौर पर जनरल इक्विलिब्रियम थियरी के सरल रूप के इस्तेमाल करेला। मैक्रोइकोनॉमिक्स में राष्ट्रीय आय, राष्ट्रीय उत्पादन, बेरोजगारी दर, मुद्रास्फीति (महँगाई) जइसन व्यापक आर्थिक सूचकन के अध्ययन कइल जाला। एकरे साथे कुल उपभोग, कुल निवेश व्यय आ उनका अलग-अलग घटकन के भी विश्लेषण कइल जाला। ई शाखा [[मॉनिटरी पॉलिसी]] आ [[फिस्कल पॉलिसी]] (राजकोषीय नीति) के अर्थव्यवस्था पर पड़े वाला प्रभाव सभ के भी अध्ययन करेला। 1960 के दशक से मैक्रोइकोनॉमिक्स में सूक्ष्म आर्थिक आधार पर मॉडल विकसित करे पर अधिक जोर दिहल जाए लागल। एहमें आर्थिक भागीदारन के तर्कसंगत व्यवहार, बाजार सूचना के प्रभावी उपयोग आ अपूर्ण प्रतियोगिता जइसन पहलुअन के शामिल कइल गइल। एह विकास से मैक्रोइकोनॉमिक्स आ माइक्रोइकोनॉमिक्स के बीच के कई पुरान सैद्धांतिक असंगतियन के दूर करे के कोशिश भइल। मैक्रोइकोनॉमिक एनालिसिस में ओह कारकन के भी अध्ययन कइल जाला जे राष्ट्रीय आय के दीर्घकालिक स्तर आ [[आर्थिक विकास]] के प्रभावित करेलें। एहमें पूँजी संचय, तकनीकी प्रगति आ श्रम शक्ति के वृद्धि प्रमुख कारक मानल जालें। {{Clear}} ==संदर्भ== {{Reflist}} {{Economics}} {{Authority control}} [[श्रेणी:अर्थशास्त्र]] [[श्रेणी:सामाजिक बिज्ञान]] [[श्रेणी:एकैडमिक बिसय]] {{बिज्ञान-आधार}} 75g4qpfq9v1hr16s8k83sjpgvv63sw1 797563 797562 2026-06-08T23:18:23Z SM7 3953 /* मैक्रोइकोनॉमिक्स */ सुधार कइल गइल 797563 wikitext text/x-wiki {{for|कौटिल्य द्वारा रचित ग्रंथ|अर्थशास्त्रम्}} [[चित्र:Supply-demand-right-shift-demand.svg|240px|thumb|alt=डिमांड आ सप्लाई ग्राफ| सप्लाई आ डिमांड के डाइग्राम; मांग में बढ़ती के परभाव देखा रहल बा।]] '''अर्थशास्त्र''' ({{Lang|en|Economics}}; ''इकोनॉमिक्स'') [[सामाजिक विज्ञान]] के साखा हऽ जेकरा भीतर बस्तु आ सेवा (गुड्स एंड सर्विसेस) कुल के उत्पादन, बितरन, बिनिमय आ उपभोग के अधयन करल जाएला। 'अर्थशास्त्र' शब्द [[संस्कृत]] के 'अर्थ' (माने [[धन]]) आ 'शास्त्र' के जोड़ से बनल बा, जेकर ठेत अर्थ होखी - 'धन के अधयन'। कवनो बिषय से जुड़ला में मुनष्यन के काजन के ज्ञान के उ बिषय के शास्त्र कहल जाएला, इहे खातिर अर्थशास्त्र में इंसान के अरथ से जुड़ल काजन के ज्ञान होखल जरूरी बा। अर्थशास्त्र के फोकस एह बात पर होला कि ढेर तरीका के[[एजेंट (अर्थशास्त्र)|आर्थिक एजेंट]] सभ के बेहवार आ आपसी तालमेल कवना तरीका के बा आ [[अर्थब्यवस्था|अरथ बेवसथा]] सभ कइसे काम करे लीं। [[माइक्रोइकोनॉमिक्स]] में अर्थब्यवस्था के बेसिक तत्व सभ के बिस्लेषण कइल जाला जेह में एकहन एजेंट आ बजार (मार्केट), इनहन के अंतर्क्रिया, आ एह अंतर्क्रिया (इंटरैक्शन) सभ के परिणाम के अध्ययन शामिल होला। एह एकहक ठो एजेंट सभ में परिवार, फर्म, बिक्रेता, खरीदार वगैरह लोग सामिल होला। एकरे बिपरीत [[मैक्रोइकोनॉमिक्स]] में पूरा अर्थब्यवस्था के बिस्लेषण कइल जाला (मने कि पूरा संपूर्ण उत्पादन, उपभोग, बचत, आ निवेश) आ पूरा अर्थब्यवस्था के परभावित करे वाला मुद्दा, [[बेरोजगारी]], बिबिध किसिम के आर्थिक नीति वगैरह के अध्ययन आ बिस्लेषण कइल जाला। अर्थशास्त्रीय विवेचना के प्रयोग समाज से संबंधित विभिन्न क्षेत्रन में कइल जायेला, जैसे:- अपराध, शिक्षा, परिवार, स्वास्थ्य, कानून, राजनीति, धर्म, सामाजिक संस्थान और युद्ध इत्यदि। == माइक्रो इकोनॉमिक्स == {{main|माइक्रोइकोनॉमिक्स}} माइक्रोइकोनॉमिक्स ({{Langx|hi|व्यष्टि अर्थशास्त्र}}) एह बात के अध्ययन करे ला कि बजार ढाँचा के हिस्सा बने वाली अलग-अलग इकाई (एंटिटी) बजार के भीतर एक-दूसरा के साथ कइसे संपर्क करे लीं आ आपस में मिल के केङ्ने बजार बेवस्था के निर्माण करे लीं। एह इकाई सभ में निजी आ सार्वजनिक दुनो तरह के भागीदार शामिल हो सकेलें। ई सभ आमतौर पर सीमित संसाधन (स्कार्सिटी) आ नियमन (रेगुलेशन) के परिस्थिति में काम करेलन। बजार में खरीद-बिक्री होखे वाला वस्तु कवनो ठोस उत्पाद, जइसे सेब, भा कवनो सेवा, जइसे मरम्मत सेवा, कानूनी सलाह भा मनोरंजन सेवा, हो सकेला। अर्थशास्त्र में कई तरह के बाजार संरचना पावल जाली। [[परफेक्ट कंपटीशन]] (पूर्ण प्रतियोगिता) वाला बजार में कवनो भागीदार एतना बड़हन ना होला कि ऊ अकेले कवनो समान प्रकार के उत्पाद के दाम तय कर सके। एह स्थिति में सभे भागीदार प्राइस टेकर (दाम स्वीकार करे वाला) होलें, काहे कि कवनो सिंगल बिक्रेता प्रोडक्ट के दाम पर प्रभाव ना डाल सके ला। हालाँकि, असल दुनिया में अधिकतर बजार परफेक्ट कंपटीशन वाला ना होके इम्परफेक्ट कंपटीशन वाला होलें। इम्परफेक्ट कंपटीशन के कई रूप बाड़ें। एकाधिकार (मोनोपोली) में कवनो वस्तु के खाली एक्के ठो बिक्रेता होला। द्वयाधिकार (डुओपोली) में दू गो बिक्रेता होलें, जबकि ओलिगोपोली में बिक्रेता लोगन के गिनती कम होला। मोनोपोलिस्टिक कंपटीशन में बहुत बिक्रेता होखे लें, बाकी ऊ सभ अलग-अलग बिसेस्ता वाला प्रोडक्ट बेचेलन। मोनोपसनी में खाली एके गो खरीदार होला, जबकि ओलिगोपसनी में खरीददारन के संख्या कम रहेला। इम्परफेक्ट कंपटीशन वाला बाजार में काम करे वाली फर्म सभ के अक्सर प्राइस मेकर (दाम निश्चित करे वाला) बने के क्षमता होले। मतलब, ऊ अपना उत्पाद के दाम पर कुछ हद तक प्रभाव डाल सकेली आ ओकरा के निर्धारित करे में भूमिका निभा सके लीं। पार्शियल इक्विलिब्रियम (आंशिक संतुलन) विश्लेषण सिस्टम में ई मान के चलल जाला कि जवन बजार के अध्ययन कइल जा रहल बा, ओकर गतिविधि के दुसरे बजारन पर कवनो प्रभाव नइखे पड़त। एह तरीका में एनालिसिस खाली एकही बजार तक सीमित रहे ला आ ओही बाजार के कुल गतिविधियन के आधार पर निष्कर्ष निकालल जाला।एकरा विपरीत, जनरल इक्विलिब्रियम थ्योरी एक्के संघे कई बजारन आ उनहन के बेहवार के अध्ययन करेला। ई तरीका सगरी बजारन के कुल गतिविधियन के एकजुट रूप में देखे ला आ एह बात के जाँच करेला कि अलग-अलग बजारन में होखे वाला बदलाव एक-दूसरा पर कइसे प्रभाव डाले लें। एह किसिम के एनालिसिस में बजारन के आपसी संबंध आ परस्पर क्रिया (इंतारैक्शन) के अध्ययन कइल जाला, जवना के परिणामस्वरूप अर्थव्यवस्था में संतुलन (इक्विलिब्रियम) स्थापित होखे ला। == मैक्रोइकोनॉमिक्स == {{main|मैक्रोइकोनॉमिक्स}} मैक्रोइकोनॉमिक्स ({{Langx|hi|समष्टि अर्थशास्त्र}}) अर्थशास्त्र के एगो दूसर प्रमुख शाखा हवे, जवन पूरा [[अर्थव्यवस्था]] के समग्र रूप में अध्ययन करे ला। ई व्यापक आर्थिक सूचकन आ उनहन के परस्पर संबंधन के ऊपर से नीचे (टॉप-डाउन) दृष्टिकोण से समझे-समझावे के कोसिस करे ला, आ आमतौर पर जनरल इक्विलिब्रियम थियरी के सरल रूप के इस्तेमाल करेला। मैक्रोइकोनॉमिक्स में राष्ट्रीय आय, राष्ट्रीय उत्पादन, बेरोजगारी दर, मुद्रास्फीति (महँगाई) जइसन व्यापक आर्थिक सूचकन के अध्ययन कइल जाला। एकरे साथे कुल उपभोग, कुल निवेश व्यय आ उनका अलग-अलग घटकन के भी विश्लेषण कइल जाला। ई शाखा [[मॉनिटरी पॉलिसी]] आ [[फिस्कल पॉलिसी]] (राजकोषीय नीति) के अर्थव्यवस्था पर पड़े वाला प्रभाव सभ के भी अध्ययन करेला। 1960 के दशक से मैक्रोइकोनॉमिक्स में सूक्ष्म आर्थिक आधार पर मॉडल विकसित करे पर अधिक जोर दिहल जाए लागल। एहमें आर्थिक भागीदारन के तर्कसंगत व्यवहार, बाजार सूचना के प्रभावी उपयोग आ अपूर्ण प्रतियोगिता जइसन पहलुअन के शामिल कइल गइल। एह विकास से मैक्रोइकोनॉमिक्स आ माइक्रोइकोनॉमिक्स के बीच के कई पुरान सैद्धांतिक असंगतियन के दूर करे के कोशिश भइल। मैक्रोइकोनॉमिक एनालिसिस में ओह कारकन के भी अध्ययन कइल जाला जे राष्ट्रीय आय के दीर्घकालिक स्तर आ [[आर्थिक विकास]] के प्रभावित करेलें। एहमें पूँजी संचय, तकनीकी प्रगति आ श्रम शक्ति के वृद्धि प्रमुख कारक मानल जालें। {{Clear}} ==संदर्भ== {{Reflist}} {{Economics}} {{Authority control}} [[श्रेणी:अर्थशास्त्र]] [[श्रेणी:सामाजिक बिज्ञान]] [[श्रेणी:एकैडमिक बिसय]] {{बिज्ञान-आधार}} 8ueudyyweytphs01b5xn1mk9o13g1hn 797564 797563 2026-06-08T23:36:37Z SM7 3953 बिस्तार कइल गइल 797564 wikitext text/x-wiki {{for|कौटिल्य द्वारा रचित ग्रंथ|अर्थशास्त्रम्}} [[चित्र:Supply-demand-right-shift-demand.svg|240px|thumb|alt=डिमांड आ सप्लाई ग्राफ| सप्लाई आ डिमांड के डाइग्राम; मांग में बढ़ती के परभाव देखा रहल बा।]] '''अर्थशास्त्र''' ({{Lang|en|Economics}}; ''इकोनॉमिक्स'') [[सामाजिक विज्ञान]] के साखा हऽ जेकरा भीतर बस्तु आ सेवा (गुड्स एंड सर्विसेस) कुल के उत्पादन, बितरन, बिनिमय आ उपभोग के अधयन करल जाएला। 'अर्थशास्त्र' शब्द [[संस्कृत]] के 'अर्थ' (माने [[धन]]) आ 'शास्त्र' के जोड़ से बनल बा, जेकर ठेत अर्थ होखी - 'धन के अधयन'। कवनो बिषय से जुड़ला में मुनष्यन के काजन के ज्ञान के उ बिषय के शास्त्र कहल जाएला, इहे खातिर अर्थशास्त्र में इंसान के अरथ से जुड़ल काजन के ज्ञान होखल जरूरी बा। अर्थशास्त्र के फोकस एह बात पर होला कि ढेर तरीका के[[एजेंट (अर्थशास्त्र)|आर्थिक एजेंट]] सभ के बेहवार आ आपसी तालमेल कवना तरीका के बा आ [[अर्थब्यवस्था|अरथ बेवसथा]] सभ कइसे काम करे लीं। [[माइक्रोइकोनॉमिक्स]] में अर्थब्यवस्था के बेसिक तत्व सभ के बिस्लेषण कइल जाला जेह में एकहन एजेंट आ बजार (मार्केट), इनहन के अंतर्क्रिया, आ एह अंतर्क्रिया (इंटरैक्शन) सभ के परिणाम के अध्ययन शामिल होला। एह एकहक ठो एजेंट सभ में परिवार, फर्म, बिक्रेता, खरीदार वगैरह लोग सामिल होला। एकरे बिपरीत [[मैक्रोइकोनॉमिक्स]] में पूरा अर्थब्यवस्था के बिस्लेषण कइल जाला (मने कि पूरा संपूर्ण उत्पादन, उपभोग, बचत, आ निवेश) आ पूरा अर्थब्यवस्था के परभावित करे वाला मुद्दा, [[बेरोजगारी]], बिबिध किसिम के आर्थिक नीति वगैरह के अध्ययन आ बिस्लेषण कइल जाला। अर्थशास्त्रीय विवेचना के प्रयोग समाज से संबंधित विभिन्न क्षेत्रन में कइल जायेला, जैसे:- अपराध, शिक्षा, परिवार, स्वास्थ्य, कानून, राजनीति, धर्म, सामाजिक संस्थान और युद्ध इत्यदि। == माइक्रो इकोनॉमिक्स == {{main|माइक्रोइकोनॉमिक्स}} माइक्रोइकोनॉमिक्स ({{Langx|hi|व्यष्टि अर्थशास्त्र}}) एह बात के अध्ययन करे ला कि बजार ढाँचा के हिस्सा बने वाली अलग-अलग इकाई (एंटिटी) बजार के भीतर एक-दूसरा के साथ कइसे संपर्क करे लीं आ आपस में मिल के केङ्ने बजार बेवस्था के निर्माण करे लीं। एह इकाई सभ में निजी आ सार्वजनिक दुनो तरह के भागीदार शामिल हो सकेलें। ई सभ आमतौर पर सीमित संसाधन (स्कार्सिटी) आ नियमन (रेगुलेशन) के परिस्थिति में काम करेलन। बजार में खरीद-बिक्री होखे वाला वस्तु कवनो ठोस उत्पाद, जइसे सेब, भा कवनो सेवा, जइसे मरम्मत सेवा, कानूनी सलाह भा मनोरंजन सेवा, हो सकेला। अर्थशास्त्र में कई तरह के बाजार संरचना पावल जाली। [[परफेक्ट कंपटीशन]] (पूर्ण प्रतियोगिता) वाला बजार में कवनो भागीदार एतना बड़हन ना होला कि ऊ अकेले कवनो समान प्रकार के उत्पाद के दाम तय कर सके। एह स्थिति में सभे भागीदार प्राइस टेकर (दाम स्वीकार करे वाला) होलें, काहे कि कवनो सिंगल बिक्रेता प्रोडक्ट के दाम पर प्रभाव ना डाल सके ला। हालाँकि, असल दुनिया में अधिकतर बजार परफेक्ट कंपटीशन वाला ना होके इम्परफेक्ट कंपटीशन वाला होलें। इम्परफेक्ट कंपटीशन के कई रूप बाड़ें। एकाधिकार (मोनोपोली) में कवनो वस्तु के खाली एक्के ठो बिक्रेता होला। द्वयाधिकार (डुओपोली) में दू गो बिक्रेता होलें, जबकि ओलिगोपोली में बिक्रेता लोगन के गिनती कम होला। मोनोपोलिस्टिक कंपटीशन में बहुत बिक्रेता होखे लें, बाकी ऊ सभ अलग-अलग बिसेस्ता वाला प्रोडक्ट बेचेलन। मोनोपसनी में खाली एके गो खरीदार होला, जबकि ओलिगोपसनी में खरीददारन के संख्या कम रहेला। इम्परफेक्ट कंपटीशन वाला बाजार में काम करे वाली फर्म सभ के अक्सर प्राइस मेकर (दाम निश्चित करे वाला) बने के क्षमता होले। मतलब, ऊ अपना उत्पाद के दाम पर कुछ हद तक प्रभाव डाल सकेली आ ओकरा के निर्धारित करे में भूमिका निभा सके लीं। पार्शियल इक्विलिब्रियम (आंशिक संतुलन) विश्लेषण सिस्टम में ई मान के चलल जाला कि जवन बजार के अध्ययन कइल जा रहल बा, ओकर गतिविधि के दुसरे बजारन पर कवनो प्रभाव नइखे पड़त। एह तरीका में एनालिसिस खाली एकही बजार तक सीमित रहे ला आ ओही बाजार के कुल गतिविधियन के आधार पर निष्कर्ष निकालल जाला।एकरा विपरीत, जनरल इक्विलिब्रियम थ्योरी एक्के संघे कई बजारन आ उनहन के बेहवार के अध्ययन करेला। ई तरीका सगरी बजारन के कुल गतिविधियन के एकजुट रूप में देखे ला आ एह बात के जाँच करेला कि अलग-अलग बजारन में होखे वाला बदलाव एक-दूसरा पर कइसे प्रभाव डाले लें। एह किसिम के एनालिसिस में बजारन के आपसी संबंध आ परस्पर क्रिया (इंतारैक्शन) के अध्ययन कइल जाला, जवना के परिणामस्वरूप अर्थव्यवस्था में संतुलन (इक्विलिब्रियम) स्थापित होखे ला। == मैक्रोइकोनॉमिक्स == {{main|मैक्रोइकोनॉमिक्स}} मैक्रोइकोनॉमिक्स ({{Langx|hi|समष्टि अर्थशास्त्र}}) अर्थशास्त्र के एगो दूसर प्रमुख शाखा हवे, जवन पूरा [[अर्थव्यवस्था]] के समग्र रूप में अध्ययन करे ला। ई व्यापक आर्थिक सूचकन आ उनहन के परस्पर संबंधन के ऊपर से नीचे (टॉप-डाउन) दृष्टिकोण से समझे-समझावे के कोसिस करे ला, आ आमतौर पर जनरल इक्विलिब्रियम थियरी के सरल रूप के इस्तेमाल करेला। मैक्रोइकोनॉमिक्स में राष्ट्रीय आय, राष्ट्रीय उत्पादन, बेरोजगारी दर, मुद्रास्फीति (महँगाई) जइसन व्यापक आर्थिक सूचकन के अध्ययन कइल जाला। एकरे साथे कुल उपभोग, कुल निवेश व्यय आ उनका अलग-अलग घटकन के भी विश्लेषण कइल जाला। ई शाखा [[मॉनिटरी पॉलिसी]] आ [[फिस्कल पॉलिसी]] (राजकोषीय नीति) के अर्थव्यवस्था पर पड़े वाला प्रभाव सभ के भी अध्ययन करेला। 1960 के दशक से मैक्रोइकोनॉमिक्स में सूक्ष्म आर्थिक आधार पर मॉडल विकसित करे पर अधिक जोर दिहल जाए लागल। एहमें आर्थिक भागीदारन के तर्कसंगत व्यवहार, बाजार सूचना के प्रभावी उपयोग आ अपूर्ण प्रतियोगिता जइसन पहलुअन के शामिल कइल गइल। एह विकास से मैक्रोइकोनॉमिक्स आ माइक्रोइकोनॉमिक्स के बीच के कई पुरान सैद्धांतिक असंगतियन के दूर करे के कोशिश भइल। मैक्रोइकोनॉमिक एनालिसिस में ओह कारकन के भी अध्ययन कइल जाला जे राष्ट्रीय आय के दीर्घकालिक स्तर आ [[आर्थिक विकास]] के प्रभावित करेलें। एहमें पूँजी संचय, तकनीकी प्रगति आ श्रम शक्ति के वृद्धि प्रमुख कारक मानल जालें। === बेरोजगारी === बेरोजगारी दर अर्थव्यवस्था में [[बेरोजगारी]] के स्तर के मापेला। ई लेबर फोर्स में शामिल ओह लोग के प्रतिशत बतावेला जे काम करे के इच्छुक आ सक्षम बाड़ें, बाकिर जिनका लगे रोजगार नइखे। लेबर फोर्स में खाली उहे लोग शामिल होलें जे सक्रिय रूप से नौकरी खोजत रहेलें। रिटायर लोग, पढ़ाई करत विद्यार्थी, भा ओह लोग जे रोजगार के अवसर ना मिले के कारण नौकरी खोजे के प्रयास छोड़ दिहले बाड़ें, लेबर फोर्स के हिस्सा ना मानल जालें। बेरोजगारी के आमतौर पर कई प्रकार में बाँटल जाला, आ हर प्रकार के पीछे अलग-अलग कारण होले। क्लासिकल बेरोजगारी तब पैदा हो सके ला जब मजदूरी के दर एतना अधिक हो जाव कि नियोक्ता (रोजगार देवे वाला) अधिक श्रमिक रखे में असमर्थ भा अनिच्छुक हो जावें। एह स्थिति में रोजगार के अवसर सीमित हो जाला। एकरे अलावा फ्रिक्शनल बेरोजगारी ओह स्थिति के कहल जाला जब उपयुक्त नौकरी उपलब्ध त रहेला, बाकिर नौकरी खोजे, जानकारी प्राप्त करे आ नियुक्ति होखे में कुछ समय लागेला। एह बीच के अवधि में व्यक्ति बेरोजगार रहेला। ई बेरोजगारी आमतौर पर अस्थायी किसिम के होले आ श्रम बाजार के सामान्य कार्यप्रणाली के हिस्सा मानल जाले। {{Clear}} ==संदर्भ== {{Reflist}} {{Economics}} {{Authority control}} [[श्रेणी:अर्थशास्त्र]] [[श्रेणी:सामाजिक बिज्ञान]] [[श्रेणी:एकैडमिक बिसय]] {{बिज्ञान-आधार}} 8qds2s569alkmn7xtroplx9jbvjqiwl गोरखनाथ 0 47678 797591 795898 2026-06-09T03:08:21Z SM7 3953 अंग्रेजी विकिपीडिया से अनुबाद क के बिस्तार कइल गइल 797591 wikitext text/x-wiki {{Infobox Hindu leader | name = गुरु गोरखनाथ | image = Gorakshanath.jpg | native_name = | caption = लक्ष्मणगढ़ के मंदिर में गोरखनाथ के मूर्ती | birth_date = ~ 11वीं सदी | honors = ''महायोगी'' | founder = नाथ मठ आ मंदिर | guru = [[मछेंदरनाथ]] | known_for = हठ योग,<!-- {{sfn|Guy L. Beck|1995|pp=102-103}}{{sfn| Encyclopedia Britannica | 2007}} --> नाथ संप्रदाय, गोरखा, [[गोरखपुर]] | literary_works = }} '''गुरु गोरखनाथ''' ({{Langx|sa|गोरक्षनाथ}}) [[हिंदू धर्म|हिंदू परंपरा]] में के जोगी लोग के [[नाथ पंथ]] के स्थापना करे वाला संत रहलें।<ref>George Weston Briggs (1938), ''Gorakhnath and the Kanphata Yogis'', 6th Edition (2009 Reprint), Motilal Banarsidass. {{ISBN|978-8120805644}}, p. 228</ref> इनके जीवन के बारे में बहुत जानकारी ना बा। कथा के मोताबिक ई [[मछेंदरनाथ]] के चेला रहलें। गोरखनाथ द्वारा स्थापित मत के माने वाला लोग जोगी, दर्शनी, भा कनफटवा कहाला आ ई लोग [[हिमालय]] के [[तराई]] के इलाका में [[पंजाब]] से ले के [[बिहार]] ले फइलल बा आ [[नेपाल|नैपालो]] में मजिगर संख्या में बा। गोरखनाथ के हिंदू परंपरा में "महायोगी" मानल जाला। ऊ [[नवनाथ]] परंपरा के नौ गो संत लोगन में आवे लें। एह आध्यात्मिक परंपरा में हिंदू देवता ([[त्रिदेव]] में सामिल) [[शिव]] के पहिला गुरु मानल जाला। गोरख के जिनगी के बारे में जे कथा-कहानी चलन में बाड़ी उनहन में बतावल जाला कि ऊ समय के बंधन से बाहर रहलन आ अलग-अलग जुग में धरती पर प्रकट भइलन। गोरखनाथ कवनो एक खास दार्शनिक सिद्धांत चाहे अंतिम सत्य पर जोर ना देलन। उनकर मानन रहे कि बिना पक्षपात के सत्य के खोज कइल मनुष्य के स्वाभाविक आ मूल्यवान लक्ष्य हवे। ऊ [[योग]], [[अध्यात्म|आध्यात्मिक अनुशासन]] आ अनुभवी गुरु के मार्गदर्शन के समाधि के आ आध्यात्मिक मुक्ति तक ले पहुँचे के सबसे जरूरी साधन मनलन। उत्तर प्रदेश के [[गोरखपुर]] शहर इनहीं के नाँव पर बसल शहर बा जहाँ परसिद्ध [[गोरखनाथ मठ]] बाटे। नेपाल में एगो जिला [[गोरखा जिला]] इनहीं के नाँव पर हवे आ नेपाल आ भारत के तराई इलाका में रहे वाला [[गोरखा]] लोग के नाँव इनहीं के नाँव पर पड़ल हवे। नेपाल के राजशाही के दौरान इनका के मूल देवता मानल जाव। [[पशुपतिनाथ मंदिर]] में गोरखनाथ के अलगा से अस्थान दिहल गइल बाटे। गुरु गोरख के प्रसिद्ध रचना सभ में ''गोरख गीता'', ''योग सिद्धांत पद्धति'', ''योगबीज'', आ ''योगचिंतामणि'' नियन कई ग्रंथ बाड़ें। गोरखनाथ के कबितई में रचल चीज सभ [[हिंदी साहित्य]] के अंग के रूप में ''[[गोरखबानी]]'' के नाँव से संकलित कइल गइल बाड़ी स। == जिनगी == ===इतिहासी मत === इतिहासकार लोग एह बात पर सहमत बा कि गोरखनाथ के जीवनकाल ईस्वी सन् के दुसरी सहस्राब्दी (मिलेनियम) के पहिला आधा हिस्सा में पड़ेला, हालाँकि ऊ ठीक कवन सदी में रहलें, एह पर मतभेद बा। पुरातात्त्विक आ साहित्यिक प्रमाणन के आधार पर कुछ विद्वान उनका के 11वीं–12वीं सदी के मानेलें, जबकि कुछ अउरी बिद्वान लोग उनका के 14वीं सदी में रखेलें। एबॉट के अनुसार, [[बाबा फरीद]] से जुड़ल दस्तावेज आ ज्ञानेश्वरी के पांडुलिपि गोरखनाथ के 13वीं सदी में स्थापित करेली। दुसरे ओर, [[जार्ज ग्रियर्सन|ग्रियर्सन]] गुजरात से मिलल प्रमाणन के आधार पर उनका के 14वीं सदी के मानेलें। गोरखनाथ के उल्लेख [[कबीर]] आ [[गुरु नानक]] के कविता सभ में भी मिलेला, जहाँ उनका के बहुत प्रभावशाली नेता आ विशाल अनुयायी समूह वाला संत के रूप में वर्णित कइल गइल बा। ऐतिहासिक ग्रंथन से संकेत मिलेला कि गोरखनाथ शुरुआत में अइसन क्षेत्र में बौद्ध परंपरा से जुड़ल रहलें जहाँ शैव धर्म के प्रभाव मजबूत रहे। बाद में ऊ हिन्दू धर्म से जुड़ गइलें आ शिव आ योग के प्रमुख समर्थक बनलें। गोरखनाथ के जीवन आ विचार पर [[कुमारिल भट्ट]] आ [[आदि शंकराचार्य]] के प्रभाव देखल जाला। ऊ योग आ अद्वैत वेदांत के दृष्टिकोण से उपनिषद के व्याख्या के समर्थन करत रहलें। गोरखनाथ के मत में मध्यकालीन भारत में द्वैतवाद आ अद्वैतवाद के बीच के विवाद व्यावहारिक दृष्टि से बहुत उपयोगी ना रहे। उनका अनुसार, असली महत्त्व एह बात के बा कि योगी कवन मार्ग अपनावेला। ऊ मानत रहलें कि आध्यात्मिक अनुशासन, साधना आ योगाभ्यास के माध्यम से, चाहे कवनो मार्ग चुनल जाव, साधक आखिरकार व्यक्तिगत चेतना के पूर्ण रूप से प्रकाशित समाधि अवस्था तक पहुँच सकेला। == इहो देखल जाय == * [[नाथ पंथ]] * [[गोरखनाथ मठ]] {{clear}} ==संदर्भ== {{Reflist|35em}} [[श्रेणी:गोरखपुर]] [[श्रेणी:उत्तर प्रदेश के लोग]] [[श्रेणी:हिंदू लोग]] [[श्रेणी:हिंदू संत]] [[श्रेणी:भारतीय हिंदू योगी]] {{hinduism-stub}} oz4vzd2jby5yo7f4t931az3iw6xslyy 797592 797591 2026-06-09T03:10:28Z SM7 3953 अंग्रेजी विकिपीडिया से अनुबाद क के बिस्तार कइल गइल 797592 wikitext text/x-wiki {{Infobox Hindu leader | name = गुरु गोरखनाथ | image = Gorakshanath.jpg | native_name = | caption = लक्ष्मणगढ़ के मंदिर में गोरखनाथ के मूर्ती | birth_date = ~ 11वीं सदी | honors = ''महायोगी'' | founder = नाथ मठ आ मंदिर | guru = [[मछेंदरनाथ]] | known_for = हठ योग,<!-- {{sfn|Guy L. Beck|1995|pp=102-103}}{{sfn| Encyclopedia Britannica | 2007}} --> नाथ संप्रदाय, गोरखा, [[गोरखपुर]] | literary_works = }} '''गुरु गोरखनाथ''' ({{Langx|sa|गोरक्षनाथ}}) [[हिंदू धर्म|हिंदू परंपरा]] में के जोगी लोग के [[नाथ पंथ]] के स्थापना करे वाला संत रहलें।<ref>George Weston Briggs (1938), ''Gorakhnath and the Kanphata Yogis'', 6th Edition (2009 Reprint), Motilal Banarsidass. {{ISBN|978-8120805644}}, p. 228</ref> इनके जीवन के बारे में बहुत जानकारी ना बा। कथा के मोताबिक ई [[मछेंदरनाथ]] के चेला रहलें। गोरखनाथ द्वारा स्थापित मत के माने वाला लोग जोगी, दर्शनी, भा कनफटवा कहाला आ ई लोग [[हिमालय]] के [[तराई]] के इलाका में [[पंजाब]] से ले के [[बिहार]] ले फइलल बा आ [[नेपाल|नैपालो]] में मजिगर संख्या में बा। गोरखनाथ के हिंदू परंपरा में "महायोगी" मानल जाला। ऊ [[नवनाथ]] परंपरा के नौ गो संत लोगन में आवे लें। एह आध्यात्मिक परंपरा में हिंदू देवता ([[त्रिदेव]] में सामिल) [[शिव]] के पहिला गुरु मानल जाला। गोरख के जिनगी के बारे में जे कथा-कहानी चलन में बाड़ी उनहन में बतावल जाला कि ऊ समय के बंधन से बाहर रहलन आ अलग-अलग जुग में धरती पर प्रकट भइलन। गोरखनाथ कवनो एक खास दार्शनिक सिद्धांत चाहे अंतिम सत्य पर जोर ना देलन। उनकर मानन रहे कि बिना पक्षपात के सत्य के खोज कइल मनुष्य के स्वाभाविक आ मूल्यवान लक्ष्य हवे। ऊ [[योग]], [[अध्यात्म|आध्यात्मिक अनुशासन]] आ अनुभवी गुरु के मार्गदर्शन के समाधि के आ आध्यात्मिक मुक्ति तक ले पहुँचे के सबसे जरूरी साधन मनलन। उत्तर प्रदेश के [[गोरखपुर]] शहर इनहीं के नाँव पर बसल शहर बा जहाँ परसिद्ध [[गोरखनाथ मठ]] बाटे। नेपाल में एगो जिला [[गोरखा जिला]] इनहीं के नाँव पर हवे आ नेपाल आ भारत के तराई इलाका में रहे वाला [[गोरखा]] लोग के नाँव इनहीं के नाँव पर पड़ल हवे। नेपाल के राजशाही के दौरान इनका के मूल देवता मानल जाव। [[पशुपतिनाथ मंदिर]] में गोरखनाथ के अलगा से अस्थान दिहल गइल बाटे। गुरु गोरख के प्रसिद्ध रचना सभ में ''गोरख गीता'', ''योग सिद्धांत पद्धति'', ''योगबीज'', आ ''योगचिंतामणि'' नियन कई ग्रंथ बाड़ें। गोरखनाथ के कबितई में रचल चीज सभ [[हिंदी साहित्य]] के अंग के रूप में ''[[गोरखबानी]]'' के नाँव से संकलित कइल गइल बाड़ी स। == जिनगी == ===इतिहासी मत === इतिहासकार लोग एह बात पर सहमत बा कि गोरखनाथ के जीवनकाल ईस्वी सन् के दुसरी सहस्राब्दी (मिलेनियम) के पहिला आधा हिस्सा में पड़ेला, हालाँकि ऊ ठीक कवन सदी में रहलें, एह पर मतभेद बा। पुरातात्त्विक आ साहित्यिक प्रमाणन के आधार पर कुछ विद्वान उनका के 11वीं–12वीं सदी के मानेलें, जबकि कुछ अउरी बिद्वान लोग उनका के 14वीं सदी में रखेलें। एबॉट के अनुसार, [[बाबा फरीद]] से जुड़ल दस्तावेज आ ज्ञानेश्वरी के पांडुलिपि गोरखनाथ के 13वीं सदी में स्थापित करेली। दुसरे ओर, [[जार्ज ग्रियर्सन|ग्रियर्सन]] गुजरात से मिलल प्रमाणन के आधार पर उनका के 14वीं सदी के मानेलें। गोरखनाथ के उल्लेख [[कबीर]] आ [[गुरु नानक]] के कविता सभ में भी मिलेला, जहाँ उनका के बहुत प्रभावशाली नेता आ विशाल अनुयायी समूह वाला संत के रूप में वर्णित कइल गइल बा। ऐतिहासिक ग्रंथन से संकेत मिलेला कि गोरखनाथ शुरुआत में अइसन क्षेत्र में बौद्ध परंपरा से जुड़ल रहलें जहाँ शैव धर्म के प्रभाव मजबूत रहे। बाद में ऊ हिन्दू धर्म से जुड़ गइलें आ शिव आ योग के प्रमुख समर्थक बनलें। गोरखनाथ के जीवन आ विचार पर [[कुमारिल भट्ट]] आ [[आदि शंकराचार्य]] के प्रभाव देखल जाला। ऊ योग आ अद्वैत वेदांत के दृष्टिकोण से उपनिषद के व्याख्या के समर्थन करत रहलें। गोरखनाथ के मत में मध्यकालीन भारत में द्वैतवाद आ अद्वैतवाद के बीच के विवाद व्यावहारिक दृष्टि से बहुत उपयोगी ना रहे। उनका अनुसार, असली महत्त्व एह बात के बा कि योगी कवन मार्ग अपनावेला। ऊ मानत रहलें कि आध्यात्मिक अनुशासन, साधना आ योगाभ्यास के माध्यम से, चाहे कवनो मार्ग चुनल जाव, साधक आखिरकार व्यक्तिगत चेतना के पूर्ण रूप से प्रकाशित समाधि अवस्था तक पहुँच सकेला। === हागियोग्राफिक विवरण === गोरखनाथ से जुड़ल संत-चरित (हागियोग्राफी) साहित्य में उनका पृथ्वी पर कई बेर प्रकट होखे के वर्णन मिलेला। एह लोककथा आ धार्मिक विवरणन में उनका जन्म के निश्चित समय भा स्थान के उल्लेख नइखे मिलत, बल्कि उनका के अलौकिक आ मानवीय सीमा से परे व्यक्तित्व के रूप में प्रस्तुत कइल गइल बा। उत्तर भारत के हागियोग्राफिक परंपरा के अनुसार गोरखनाथ के उत्पत्ति उत्तर-पश्चिम भारत, खासकर पंजाब क्षेत्र से मानल जाला, जबकि कुछ विवरण में पेशावर के भी उल्लेख बा। एकरा विपरीत, बंगाल आ बिहार के कुछ हागियोग्राफी में उनका के भारत के पूर्वी क्षेत्र, विशेष रूप से असम, से संबंधित बतावल गइल बा। उपलब्ध हागियोग्राफिक स्रोत गोरखनाथ के आध्यात्मिक गुरु-परंपरा के बारे में भी अलग-अलग विवरण देलें। सभे विवरण आदिनाथ आ मत्स्येन्द्रनाथ के गोरखनाथ से पहिले के गुरु के रूप में स्वीकार करेलें। हालाँकि, कुछ परंपरन में आदिनाथ से पहिले पाँच गुरु के उल्लेख मिलेला, जबकि दोसर विवरण में मत्स्येन्द्रनाथ आ गोरखनाथ के बीच छह गो गुरु के नाम बतावल गइल बा। वर्तमान नाथ परंपरा के अनुसार आदिनाथ, जिनका के आमतौर पर शिव के रूप मानल जाला, मत्स्येन्द्रनाथ के प्रत्यक्ष गुरु रहलें। मत्स्येन्द्रनाथ के बाद गोरखनाथ उनका प्रत्यक्ष शिष्य आ उत्तराधिकारी मानल जालें। == इहो देखल जाय == * [[नाथ पंथ]] * [[गोरखनाथ मठ]] {{clear}} ==संदर्भ== {{Reflist|35em}} [[श्रेणी:गोरखपुर]] [[श्रेणी:उत्तर प्रदेश के लोग]] [[श्रेणी:हिंदू लोग]] [[श्रेणी:हिंदू संत]] [[श्रेणी:भारतीय हिंदू योगी]] {{hinduism-stub}} l0aus9j8mt3mowv77gfj7prltqjdbz9 797593 797592 2026-06-09T03:40:28Z SM7 3953 संदर्भ जोड़ल/सुधारल गइल 797593 wikitext text/x-wiki {{Infobox Hindu leader | name = गुरु गोरखनाथ | image = Gorakshanath.jpg | native_name = | caption = लक्ष्मणगढ़ के मंदिर में गोरखनाथ के मूर्ती | birth_date = ~ 11वीं सदी | honors = ''महायोगी'' | founder = नाथ मठ आ मंदिर | guru = [[मछेंदरनाथ]] | known_for = हठ योग,<!-- {{sfn|Guy L. Beck|1995|pp=102-103}}{{sfn| Encyclopedia Britannica | 2007}} --> नाथ संप्रदाय, गोरखा, [[गोरखपुर]] | literary_works = }} '''गुरु गोरखनाथ''' ({{Langx|sa|गोरक्षनाथ}}) [[हिंदू धर्म|हिंदू परंपरा]] में के जोगी लोग के [[नाथ पंथ]] के स्थापना करे वाला संत रहलें।<ref>George Weston Briggs (1938), ''Gorakhnath and the Kanphata Yogis'', 6th Edition (2009 Reprint), Motilal Banarsidass. {{ISBN|978-8120805644}}, p. 228</ref> इनके जीवन के बारे में बहुत जानकारी ना बा। कथा के मोताबिक ई [[मछेंदरनाथ]] के चेला रहलें। गोरखनाथ द्वारा स्थापित मत के माने वाला लोग जोगी, दर्शनी, भा कनफटवा कहाला आ ई लोग [[हिमालय]] के [[तराई]] के इलाका में [[पंजाब]] से ले के [[बिहार]] ले फइलल बा आ [[नेपाल|नैपालो]] में मजिगर संख्या में बा। गोरखनाथ के हिंदू परंपरा में "महायोगी" मानल जाला। ऊ [[नवनाथ]] परंपरा के नौ गो संत लोगन में आवे लें। एह आध्यात्मिक परंपरा में हिंदू देवता ([[त्रिदेव]] में सामिल) [[शिव]] के पहिला गुरु मानल जाला। गोरख के जिनगी के बारे में जे कथा-कहानी चलन में बाड़ी उनहन में बतावल जाला कि ऊ समय के बंधन से बाहर रहलन आ अलग-अलग जुग में धरती पर प्रकट भइलन। गोरखनाथ कवनो एक खास दार्शनिक सिद्धांत चाहे अंतिम सत्य पर जोर ना देलन। उनकर मानन रहे कि बिना पक्षपात के सत्य के खोज कइल मनुष्य के स्वाभाविक आ मूल्यवान लक्ष्य हवे। ऊ [[योग]], [[अध्यात्म|आध्यात्मिक अनुशासन]] आ अनुभवी गुरु के मार्गदर्शन के समाधि के आ आध्यात्मिक मुक्ति तक ले पहुँचे के सबसे जरूरी साधन मनलन। उत्तर प्रदेश के [[गोरखपुर]] शहर इनहीं के नाँव पर बसल शहर बा जहाँ परसिद्ध [[गोरखनाथ मठ]] बाटे। नेपाल में एगो जिला [[गोरखा जिला]] इनहीं के नाँव पर हवे आ नेपाल आ भारत के तराई इलाका में रहे वाला [[गोरखा]] लोग के नाँव इनहीं के नाँव पर पड़ल हवे। नेपाल के राजशाही के दौरान इनका के मूल देवता मानल जाव। [[पशुपतिनाथ मंदिर]] में गोरखनाथ के अलगा से अस्थान दिहल गइल बाटे। गुरु गोरख के प्रसिद्ध रचना सभ में ''गोरख गीता'', ''योग सिद्धांत पद्धति'', ''योगबीज'', आ ''योगचिंतामणि'' नियन कई ग्रंथ बाड़ें। गोरखनाथ के कबितई में रचल चीज सभ [[हिंदी साहित्य]] के अंग के रूप में ''[[गोरखबानी]]'' के नाँव से संकलित कइल गइल बाड़ी स। == जिनगी == ===इतिहासी मत === इतिहासकार लोग एह बात पर सहमत बा कि गोरखनाथ के जीवनकाल ईस्वी सन् के दुसरी सहस्राब्दी (मिलेनियम) के पहिला आधा हिस्सा में पड़ेला, हालाँकि ऊ ठीक कवन सदी में रहलें, एह पर मतभेद बा। पुरातात्त्विक आ साहित्यिक प्रमाणन के आधार पर कुछ विद्वान उनका के 11वीं–12वीं सदी के मानेलें,{{sfn|Briggs|1938|p=249}} जबकि कुछ अउरी बिद्वान लोग उनका के 14वीं सदी में रखेलें।{{sfn|Briggs|1938|pp=228–230}} एबॉट के अनुसार, [[बाबा फरीद]] से जुड़ल दस्तावेज आ ज्ञानेश्वरी के पांडुलिपि गोरखनाथ के 13वीं सदी में स्थापित करेली।{{sfn|Briggs|1938|pp=230, 242–243}} दुसरे ओर, [[जार्ज ग्रियर्सन|ग्रियर्सन]] गुजरात से मिलल प्रमाणन के आधार पर उनका के 14वीं सदी के मानेलें।{{sfn|Briggs|1938|pp=230, 242–243}} गोरखनाथ के उल्लेख [[कबीर]] आ [[गुरु नानक]] के कविता सभ में भी मिलेला, जहाँ उनका के बहुत प्रभावशाली नेता आ विशाल अनुयायी समूह वाला संत के रूप में वर्णित कइल गइल बा।{{sfn|Briggs|1938|pp=236–242}} ऐतिहासिक ग्रंथन से संकेत मिलेला कि गोरखनाथ शुरुआत में अइसन क्षेत्र में [[बौद्ध धर्म|बौद्ध परंपरा]] से जुड़ल रहलें जहाँ [[शैव मत|शैव धर्म]] के प्रभाव मजबूत रहे। बाद में ऊ [[हिंदू धर्म]] से जुड़ गइलें आ [[शिव]] आ [[योग]] के प्रमुख समर्थक बनलें।{{sfn|Briggs|1938|pp=229, 233–235}} गोरखनाथ के जीवन आ विचार पर [[कुमारिल भट्ट]] आ [[आदि शंकराचार्य]] के प्रभाव देखल जाला जेकरा चलते ऊ योग आ [[अद्वैत वेदांत]] के दृष्टिकोण से [[उपनिषद]] सभ के व्याख्या के समर्थन कइलें।{{sfn|Akshaya Kumar Banerjea|1983|pp=xli, 303–307}} गोरखनाथ के मत में मध्यकालीन भारत में [[द्वैतवाद]] आ [[अद्वैतवाद]] के बीच के विवाद व्यावहारिक दृष्टि से बहुत उपयोगी ना रहे। उनका अनुसार, असली महत्त्व एह बात के बा कि योगी कवन मार्ग अपनावेला। ऊ मानत रहलें कि आध्यात्मिक अनुशासन, साधना आ योगाभ्यास के माध्यम से, चाहे कवनो मार्ग चुनल जाव, साधक आखिरकार व्यक्तिगत चेतना के पूर्ण रूप से प्रकाशित समाधि अवस्था तक पहुँच सकेला।{{sfn|Akshaya Kumar Banerjea|1983|pp=xli, 307–312}} === हागियोग्राफिक विवरण === गोरखनाथ से जुड़ल संत-चरित (हागियोग्राफी) साहित्य में उनका पृथ्वी पर कई बेर प्रकट होखे के वर्णन मिलेला। एह लोककथा आ धार्मिक विवरणन में उनका जन्म के निश्चित समय भा स्थान के उल्लेख नइखे मिलत, बल्कि उनका के अलौकिक आ मानवीय सीमा से परे व्यक्तित्व के रूप में प्रस्तुत कइल गइल बा। उत्तर भारत के हागियोग्राफिक परंपरा के अनुसार गोरखनाथ के उत्पत्ति उत्तर-पश्चिम भारत, खासकर पंजाब क्षेत्र से मानल जाला, जबकि कुछ विवरण में पेशावर के भी उल्लेख बा। एकरा विपरीत, बंगाल आ बिहार के कुछ हागियोग्राफी में उनका के भारत के पूर्वी क्षेत्र, विशेष रूप से असम, से संबंधित बतावल गइल बा। उपलब्ध हागियोग्राफिक स्रोत गोरखनाथ के आध्यात्मिक गुरु-परंपरा के बारे में भी अलग-अलग विवरण देलें। सभे विवरण आदिनाथ आ मत्स्येन्द्रनाथ के गोरखनाथ से पहिले के गुरु के रूप में स्वीकार करेलें। हालाँकि, कुछ परंपरन में आदिनाथ से पहिले पाँच गुरु के उल्लेख मिलेला, जबकि दोसर विवरण में मत्स्येन्द्रनाथ आ गोरखनाथ के बीच छह गो गुरु के नाम बतावल गइल बा। वर्तमान नाथ परंपरा के अनुसार आदिनाथ, जिनका के आमतौर पर शिव के रूप मानल जाला, मत्स्येन्द्रनाथ के प्रत्यक्ष गुरु रहलें। मत्स्येन्द्रनाथ के बाद गोरखनाथ उनका प्रत्यक्ष शिष्य आ उत्तराधिकारी मानल जालें। == इहो देखल जाय == * [[नाथ पंथ]] * [[गोरखनाथ मठ]] {{clear}} ==संदर्भ== {{Reflist|35em}} [[श्रेणी:गोरखपुर]] [[श्रेणी:उत्तर प्रदेश के लोग]] [[श्रेणी:हिंदू लोग]] [[श्रेणी:हिंदू संत]] [[श्रेणी:भारतीय हिंदू योगी]] {{hinduism-stub}} oewqpwd7i9lxqsqmcmg6lt1re7tee77 797596 797593 2026-06-09T06:44:07Z SM7 3953 भाषा/ब्याकरण संबंधी सुधार कइल गइल 797596 wikitext text/x-wiki {{Infobox Hindu leader | name = गुरु गोरखनाथ | image = Gorakshanath.jpg | native_name = | caption = लक्ष्मणगढ़ के मंदिर में गोरखनाथ के मूर्ती | birth_date = ~ 11वीं सदी | honors = ''महायोगी'' | founder = नाथ मठ आ मंदिर | guru = [[मछेंदरनाथ]] | known_for = हठ योग,<!-- {{sfn|Guy L. Beck|1995|pp=102-103}}{{sfn| Encyclopedia Britannica | 2007}} --> नाथ संप्रदाय, गोरखा, [[गोरखपुर]] | literary_works = }} '''गुरु गोरखनाथ''' ({{Langx|sa|गोरक्षनाथ}}) [[हिंदू धर्म|हिंदू परंपरा]] में के जोगी लोग के [[नाथ पंथ]] के स्थापना करे वाला संत रहलें।<ref>George Weston Briggs (1938), ''Gorakhnath and the Kanphata Yogis'', 6th Edition (2009 Reprint), Motilal Banarsidass. {{ISBN|978-8120805644}}, p. 228</ref> इनके जीवन के बारे में बहुत जानकारी ना बा। कथा के मोताबिक ई [[मछेंदरनाथ]] के चेला रहलें। गोरखनाथ द्वारा स्थापित मत के माने वाला लोग जोगी, दर्शनी, भा कनफटवा कहाला आ ई लोग [[हिमालय]] के [[तराई]] के इलाका में [[पंजाब]] से ले के [[बिहार]] ले फइलल बा आ [[नेपाल|नैपालो]] में मजिगर संख्या में बा। गोरखनाथ के हिंदू परंपरा में "महायोगी" मानल जाला। ऊ [[नवनाथ]] परंपरा के नौ गो संत लोगन में आवे लें। एह आध्यात्मिक परंपरा में हिंदू देवता ([[त्रिदेव]] में सामिल) [[शिव]] के पहिला गुरु मानल जाला। गोरख के जिनगी के बारे में जे कथा-कहानी चलन में बाड़ी उनहन में बतावल जाला कि ऊ समय के बंधन से बाहर रहलन आ अलग-अलग जुग में धरती पर प्रकट भइलन। गोरखनाथ कवनो एक खास दार्शनिक सिद्धांत चाहे अंतिम सत्य पर जोर ना देलन। उनकर मानन रहे कि बिना पक्षपात के सत्य के खोज कइल मनुष्य के स्वाभाविक आ मूल्यवान लक्ष्य हवे। ऊ [[योग]], [[अध्यात्म|आध्यात्मिक अनुशासन]] आ अनुभवी गुरु के मार्गदर्शन के समाधि के आ आध्यात्मिक मुक्ति तक ले पहुँचे के सबसे जरूरी साधन मनलन। उत्तर प्रदेश के [[गोरखपुर]] शहर इनहीं के नाँव पर बसल शहर बा जहाँ परसिद्ध [[गोरखनाथ मठ]] बाटे। नेपाल में एगो जिला [[गोरखा जिला]] इनहीं के नाँव पर हवे आ नेपाल आ भारत के तराई इलाका में रहे वाला [[गोरखा]] लोग के नाँव इनहीं के नाँव पर पड़ल हवे। नेपाल के राजशाही के दौरान इनका के मूल देवता मानल जाव। [[पशुपतिनाथ मंदिर]] में गोरखनाथ के अलगा से अस्थान दिहल गइल बाटे। गुरु गोरख के प्रसिद्ध रचना सभ में ''गोरख गीता'', ''योग सिद्धांत पद्धति'', ''योगबीज'', आ ''योगचिंतामणि'' नियन कई ग्रंथ बाड़ें। गोरखनाथ के कबितई में रचल चीज सभ [[हिंदी साहित्य]] के अंग के रूप में ''[[गोरखबानी]]'' के नाँव से संकलित कइल गइल बाड़ी स। == जिनगी == ===इतिहासी मत === इतिहासकार लोग एह बात पर सहमत बा कि गोरखनाथ के जीवनकाल ईस्वी सन् के दुसरी सहस्राब्दी (मिलेनियम) के पहिला आधा हिस्सा में पड़ेला, हालाँकि ऊ ठीक कवन सदी में रहलें, एह पर मतभेद बा। पुरातात्त्विक आ साहित्यिक प्रमाणन के आधार पर कुछ विद्वान उनका के 11वीं–12वीं सदी के मानेलें,{{sfn|Briggs|1938|p=249}} जबकि कुछ अउरी बिद्वान लोग उनका के 14वीं सदी में रखेलें।{{sfn|Briggs|1938|pp=228–230}} एबॉट के अनुसार, [[बाबा फरीद]] से जुड़ल दस्तावेज आ ज्ञानेश्वरी के पांडुलिपि गोरखनाथ के 13वीं सदी में स्थापित करेली।{{sfn|Briggs|1938|pp=230, 242–243}} दुसरे ओर, [[जार्ज ग्रियर्सन|ग्रियर्सन]] गुजरात से मिलल प्रमाणन के आधार पर उनका के 14वीं सदी के मानेलें।{{sfn|Briggs|1938|pp=230, 242–243}} गोरखनाथ के उल्लेख [[कबीर]] आ [[गुरु नानक]] के कविता सभ में भी मिलेला, जहाँ उनका के बहुत प्रभावशाली नेता आ विशाल अनुयायी समूह वाला संत के रूप में वर्णित कइल गइल बा।{{sfn|Briggs|1938|pp=236–242}} ऐतिहासिक ग्रंथन से संकेत मिलेला कि गोरखनाथ शुरुआत में अइसन क्षेत्र में [[बौद्ध धर्म|बौद्ध परंपरा]] से जुड़ल रहलें जहाँ [[शैव मत|शैव धर्म]] के प्रभाव मजबूत रहे। बाद में ऊ [[हिंदू धर्म]] से जुड़ गइलें आ [[शिव]] आ [[योग]] के प्रमुख समर्थक बनलें।{{sfn|Briggs|1938|pp=229, 233–235}} गोरखनाथ के जीवन आ विचार पर [[कुमारिल भट्ट]] आ [[आदि शंकराचार्य]] के प्रभाव देखल जाला जेकरा चलते ऊ योग आ [[अद्वैत वेदांत]] के दृष्टिकोण से [[उपनिषद]] सभ के व्याख्या के समर्थन कइलें।{{sfn|Akshaya Kumar Banerjea|1983|pp=xli, 303–307}} गोरखनाथ के मत में मध्यकालीन भारत में [[द्वैतवाद]] आ [[अद्वैतवाद]] के बीच के विवाद व्यावहारिक दृष्टि से बहुत उपयोगी ना रहे। उनका अनुसार, असली महत्त्व एह बात के बा कि योगी कवन मार्ग अपनावेला। ऊ मानत रहलें कि आध्यात्मिक अनुशासन, साधना आ योगाभ्यास के माध्यम से, चाहे कवनो मार्ग चुनल जाव, साधक आखिरकार व्यक्तिगत चेतना के पूर्ण रूप से प्रकाशित समाधि अवस्था तक पहुँच सकेला।{{sfn|Akshaya Kumar Banerjea|1983|pp=xli, 307–312}} === हैगियोग्राफिक विवरण === गोरखनाथ से जुड़ल संत-चरित ([[हैगियोग्राफी]]) साहित्य में उनकरा पृथ्वी पर कई बेर प्रकट होखे के वर्णन मिलेला। एह लोककथा आ धार्मिक विवरणन में उनका जनम के निश्चित समय भा जगह (लोकेशन) के उल्लेख ना मिले ला, बलुक उनका के अलौकिक आ मानवीय सीमा से परे व्यक्तित्व के रूप में प्रस्तुत कइल जाला। उत्तर भारत के हैगियोग्राफिक परंपरा के अनुसार गोरखनाथ के उत्पत्ति उत्तर-पश्चिम भारत, खासकर पंजाब क्षेत्र से मानल जाला, जबकि कुछ विवरण में पेशावर के भी उल्लेख मिले ला। एकरा विपरीत, बंगाल आ बिहार के कुछ हैगियोग्राफी में उनका के भारत के पूर्वी क्षेत्र, विशेष रूप से आसाम, से संबंधित बतावल गइल बा। उपलब्ध हैगियोग्राफिक स्रोत गोरखनाथ के आध्यात्मिक गुरु-परंपरा के बारे में भी अलग-अलग विवरण देलें। सभे बिबरन [[आदिनाथ]] आ [[मछेंदरनाथ]] के गोरखनाथ से पहिले के गुरु के रूप में स्वीकार करे लें। हालाँकि, कुछ परंपरन में आदिनाथ से पहिले पाँच गो अउरी गुरु लोगन के जिकिर मिलेला, जबकि दुसरे बिबरन में मछेंदरनाथ आ गोरखनाथ के बीचा में छह गो गुरु लोग के नाँव बतावल गइल बा। वर्तमान नाथ परंपरा के अनुसार आदिनाथ, जिनका के आमतौर पर [[शिव|शिवजी]] के रूप मानल जाला, मछेंदरनाथ के के प्रत्यक्ष गुरु रहलें। मछेंदर के बाद गोरखनाथ उनका प्रत्यक्ष शिष्य आ उत्तराधिकारी मानल जालें। == इहो देखल जाय == * [[नाथ पंथ]] * [[गोरखनाथ मठ]] {{clear}} ==संदर्भ== {{Reflist|35em}} [[श्रेणी:गोरखपुर]] [[श्रेणी:उत्तर प्रदेश के लोग]] [[श्रेणी:हिंदू लोग]] [[श्रेणी:हिंदू संत]] [[श्रेणी:भारतीय हिंदू योगी]] {{hinduism-stub}} 91h8st7529sn7zmmwft16xcqq3oswa7 सूरदास 0 51494 797565 683951 2026-06-09T00:05:07Z SM7 3953 बिस्तार कइल गइल 797565 wikitext text/x-wiki {{Infobox religious biography | religion = [[हिंदू धर्म]] | name = सूरदास | image = | alt = | caption = एकतारा बजावत आ गावत सूरदास, कल्पित चित्र | birth_date = अनिश्चित (1478-1483) | birth_place = मान्यता [[ब्रज]], [[उत्तर प्रदेश]], [[भारत]] | death_date = अनिश्चित (1561-1584) | death_place = ब्रज, उत्तर प्रदेश, भारत | known_for = Influencing the [[Bhakti movement]], [[Sant Mat]], Hymns in the [[Guru Granth Sahib]] | literary_works = ''सूरसागर'', ''सूरसारावली'', ''साहित्य लहरी'' | philosophy = [[भक्ति]] | father = | mother = }} '''सूरदास''' 16वीं सदी के प्रसिद्ध [[भक्त]] [[कवि]] आ गायक रहलें। सूर नाँव पड़े के कारन उनुकर आँख से आन्हर भइल रहे। ऊ खास तौर पर [[कृष्ण]] के भक्ति में लिखल अपना पद आ [[भजन]] सभ खातिर जानल जालें। उनकर अधिकतर भक्ति गीत [[ब्रजभाषा]] में रचल गइल हवें, जबकि कुछ रचना मध्यकालीन हिंदी के अउरी दूसर बोली, जइसे [[अवधी भाषा|अवधी]], में भी मिले लीं। सूरदास के जिनगी के कथा के वर्णन अक्सर पुष्टिमार्ग (वल्लभ संप्रदाय) के परंपरा के आधार पर कइल जाला। पुष्टिमार्ग के अनुसार सूरदास, वल्लभाचार्य के दीक्षित चेला रहलें। उनकर जीवन कथा चौरासी वैष्णवन की वार्ता जइसन ग्रंथन में मिलेला। सूरदास के पद, [[अष्टछाप]] के अउरी बाकी कवियन के रचना सभ के साथे, पुष्टिमार्ग के भजन-कीर्तन परंपरा के प्रमुख हिस्सा मानल जालें। हालाँकि, आधुनिक बिद्वान लोग सूरदास आ वल्लभाचार्य के बीच एह संबंध के ऐतिहासिक रूप से प्रमाणित ना मानेलें। ''[[सूरसागर]]'' के परंपरागत रूप से सूरदास के रचल सभसे प्रमुख ग्रंथ मानल जाला। हालाँकि, विद्वान लोग के मान्यता ई बा कि एह ग्रंथ में शामिल कई पद बाद के कवियन द्वारा सूरदास के नाम से लिखल गइल हो सके लें। वर्तमान रूप में सूरसागर मुख्य रूप से गोकुल आ ब्रज के बालक कृष्ण के मनोहर लीला सभ के वर्णन करेला। एह पदन में अक्सर गोपी लोग के दृष्टिकोण से कृष्ण के बाल रूप आ उनकर प्रेममय लीलान के चित्रण मिलेला। {{clear}} [[श्रेणी:हिंदी-भाषा के कवि]] [[श्रेणी:भक्ति आंदोलन]] {{poet-stub}} nnnrxeqzi8vqlb8uzeonqivy5j5tdsm 797566 797565 2026-06-09T00:11:41Z SM7 3953 सुधार कइल गइल 797566 wikitext text/x-wiki {{Infobox religious biography | religion = [[हिंदू धर्म]] | name = सूरदास | image = Surdas, detail of a painting of Surdas with a devoted Brahmin, Kishangarh style, Rajasthan, circa 18th century (cropped).jpg | alt = Surdas | caption = सूरदास के एगो पेंटिंग, किशनगढ़ स्टाइल, राजस्थान, लगभग 18वीं सदी | birth_date = {{circa|1483}} | birth_place = सीही गाँव, [[फरीदाबाद]], [[दिल्ली सल्तनत]] | death_date = {{circa|1563}} | death_place = [[ब्रज]] परसौली, [[मुगल राज]] | known_for = [[भक्ति आंदोलन]] के अगुआ, [[संत मत]], | literary_works = ''[[सूरसागर]]'', ''सूर सारावली'', ''साहित्य लहरी'' | philosophy = [[भक्ति]] | father = रामदास सारस्वत<ref name="knowledgeocean.in">{{Cite web|url=https://knowledgeocean.in/biography-of-surdas-in-hindi-jivan-parichay-%E0%A4%B8%E0%A5%82%E0%A4%B0%E0%A4%A6%E0%A4%BE%E0%A4%B8-%E0%A4%95%E0%A4%BE-%E0%A4%9C%E0%A5%80%E0%A4%B5%E0%A4%A8-%E0%A4%AA%E0%A4%B0%E0%A4%BF%E0%A4%9A%E0%A4%AF/3290/|archive-url=https://web.archive.org/web/20200921003713/https://knowledgeocean.in/biography-of-surdas-in-hindi-jivan-parichay-%E0%A4%B8%E0%A5%82%E0%A4%B0%E0%A4%A6%E0%A4%BE%E0%A4%B8-%E0%A4%95%E0%A4%BE-%E0%A4%9C%E0%A5%80%E0%A4%B5%E0%A4%A8-%E0%A4%AA%E0%A4%B0%E0%A4%BF%E0%A4%9A%E0%A4%AF/3290/|url-status=usurped|archive-date=21 September 2020|title = सूरदास का जीवन परिचय - Biography of Surdas in Hindi Jivan Parichay| date=16 September 2020 }}</ref> | mother = जमुनादास<ref name="knowledgeocean.in"/> }} '''सूरदास''' 16वीं सदी के प्रसिद्ध [[भक्त]] [[कवि]] आ गायक रहलें। सूर नाँव पड़े के कारन उनुकर आँख से आन्हर भइल रहे। ऊ खास तौर पर [[कृष्ण]] के भक्ति में लिखल अपना पद आ [[भजन]] सभ खातिर जानल जालें। उनकर अधिकतर भक्ति गीत [[ब्रजभाषा]] में रचल गइल हवें, जबकि कुछ रचना मध्यकालीन हिंदी के अउरी दूसर बोली, जइसे [[अवधी भाषा|अवधी]], में भी मिले लीं। सूरदास के जिनगी के कथा के वर्णन अक्सर पुष्टिमार्ग (वल्लभ संप्रदाय) के परंपरा के आधार पर कइल जाला। पुष्टिमार्ग के अनुसार सूरदास, वल्लभाचार्य के दीक्षित चेला रहलें। उनकर जीवन कथा चौरासी वैष्णवन की वार्ता जइसन ग्रंथन में मिलेला। सूरदास के पद, [[अष्टछाप]] के अउरी बाकी कवियन के रचना सभ के साथे, पुष्टिमार्ग के भजन-कीर्तन परंपरा के प्रमुख हिस्सा मानल जालें। हालाँकि, आधुनिक बिद्वान लोग सूरदास आ वल्लभाचार्य के बीच एह संबंध के ऐतिहासिक रूप से प्रमाणित ना मानेलें। ''[[सूरसागर]]'' के परंपरागत रूप से सूरदास के रचल सभसे प्रमुख ग्रंथ मानल जाला। हालाँकि, विद्वान लोग के मान्यता ई बा कि एह ग्रंथ में शामिल कई पद बाद के कवियन द्वारा सूरदास के नाम से लिखल गइल हो सके लें। वर्तमान रूप में सूरसागर मुख्य रूप से गोकुल आ ब्रज के बालक कृष्ण के मनोहर लीला सभ के वर्णन करेला। एह पदन में अक्सर गोपी लोग के दृष्टिकोण से कृष्ण के बाल रूप आ उनकर प्रेममय लीला सभ के चित्रण मिलेला। {{clear}} == संदर्भ == {{Reflist|29em}} [[श्रेणी:हिंदी-भाषा के कवि]] [[श्रेणी:भक्ति आंदोलन]] {{poet-stub}} 1pmm1kol48d2qxd58l6o5u2x242yejc 797567 797566 2026-06-09T00:42:39Z SM7 3953 संदर्भ जोड़ल/सुधारल गइल 797567 wikitext text/x-wiki {{Infobox religious biography | religion = [[हिंदू धर्म]] | name = सूरदास | image = Surdas, detail of a painting of Surdas with a devoted Brahmin, Kishangarh style, Rajasthan, circa 18th century (cropped).jpg | alt = Surdas | caption = सूरदास के एगो पेंटिंग, किशनगढ़ स्टाइल, राजस्थान, लगभग 18वीं सदी | birth_date = {{circa|1483}} | birth_place = सीही गाँव, [[फरीदाबाद]], [[दिल्ली सल्तनत]] | death_date = {{circa|1563}} | death_place = [[ब्रज]] परसौली, [[मुगल राज]] | known_for = [[भक्ति आंदोलन]] के अगुआ, [[संत मत]], | literary_works = ''[[सूरसागर]]'', ''सूर सारावली'', ''साहित्य लहरी'' | philosophy = [[भक्ति]] | father = रामदास सारस्वत<ref name="knowledgeocean.in">{{Cite web|url=https://knowledgeocean.in/biography-of-surdas-in-hindi-jivan-parichay-%E0%A4%B8%E0%A5%82%E0%A4%B0%E0%A4%A6%E0%A4%BE%E0%A4%B8-%E0%A4%95%E0%A4%BE-%E0%A4%9C%E0%A5%80%E0%A4%B5%E0%A4%A8-%E0%A4%AA%E0%A4%B0%E0%A4%BF%E0%A4%9A%E0%A4%AF/3290/|archive-url=https://web.archive.org/web/20200921003713/https://knowledgeocean.in/biography-of-surdas-in-hindi-jivan-parichay-%E0%A4%B8%E0%A5%82%E0%A4%B0%E0%A4%A6%E0%A4%BE%E0%A4%B8-%E0%A4%95%E0%A4%BE-%E0%A4%9C%E0%A5%80%E0%A4%B5%E0%A4%A8-%E0%A4%AA%E0%A4%B0%E0%A4%BF%E0%A4%9A%E0%A4%AF/3290/|url-status=usurped|archive-date=21 September 2020|title = सूरदास का जीवन परिचय - Biography of Surdas in Hindi Jivan Parichay| date=16 September 2020 }}</ref> | mother = जमुनादास<ref name="knowledgeocean.in"/> }} '''सूरदास''' 16वीं सदी के प्रसिद्ध [[भक्त]] [[कवि]] आ गायक रहलें।<ref name=Klaustermaier>{{cite book|author=Klaus K. Klostermaier|title=A Survey of Hinduism: Third Edition| url=https://books.google.com/books?id=E_6-JbUiHB4C&pg=PA215| date=2007-07-05| publisher=SUNY Press| isbn=978-0-7914-7082-4|page=215}}</ref> सूर नाँव पड़े के कारन उनुकर आँख से आन्हर भइल रहे। ऊ खास तौर पर [[कृष्ण]] के भक्ति में लिखल अपना पद आ [[भजन]] सभ खातिर जानल जालें। उनकर अधिकतर भक्ति गीत [[ब्रजभाषा]] में रचल गइल हवें, जबकि कुछ रचना मध्यकालीन हिंदी के अउरी दूसर बोली, जइसे [[अवधी भाषा|अवधी]], में भी मिले लीं।<ref name=":0">{{Cite web |date=2018-06-17 |title=Surdas Biography - Surdas Poems - Life History in English |url=https://indiathedestiny.com/icons/poets-writers/surdas-biography/ |access-date=2022-04-26 |website=India the Destiny |language=en-US |archive-date=26 June 2022 |archive-url=https://web.archive.org/web/20220626144617/https://indiathedestiny.com/icons/poets-writers/surdas-biography/ |url-status=dead }}</ref> सूरदास के जिनगी के कथा के वर्णन अक्सर पुष्टिमार्ग (वल्लभ संप्रदाय) के परंपरा के आधार पर कइल जाला। पुष्टिमार्ग के अनुसार सूरदास, वल्लभाचार्य के दीक्षित चेला रहलें। उनकर जीवन कथा चौरासी वैष्णवन की वार्ता जइसन ग्रंथन में मिलेला। सूरदास के पद, [[अष्टछाप]] के अउरी बाकी कवियन के रचना सभ के साथे, पुष्टिमार्ग के भजन-कीर्तन परंपरा के प्रमुख हिस्सा मानल जालें। हालाँकि, आधुनिक बिद्वान लोग सूरदास आ वल्लभाचार्य के बीच एह संबंध के ऐतिहासिक रूप से प्रमाणित ना मानेलें। ''[[सूरसागर]]'' के परंपरागत रूप से सूरदास के रचल सभसे प्रमुख ग्रंथ मानल जाला। हालाँकि, विद्वान लोग के मान्यता ई बा कि एह ग्रंथ में शामिल कई पद बाद के कवियन द्वारा सूरदास के नाम से लिखल गइल हो सके लें। वर्तमान रूप में सूरसागर मुख्य रूप से गोकुल आ ब्रज के बालक कृष्ण के मनोहर लीला सभ के वर्णन करेला। एह पदन में अक्सर गोपी लोग के दृष्टिकोण से कृष्ण के बाल रूप आ उनकर प्रेममय लीला सभ के चित्रण मिलेला। {{clear}} == संदर्भ == {{Reflist|29em}} [[श्रेणी:हिंदी-भाषा के कवि]] [[श्रेणी:भक्ति आंदोलन]] {{poet-stub}} dfjzixxzqdwmneahev60w0cklbdbexd 797568 797567 2026-06-09T00:55:19Z SM7 3953 अंग्रेजी विकिपीडिया से अनुबाद क के बिस्तार कइल गइल 797568 wikitext text/x-wiki {{Infobox religious biography | religion = [[हिंदू धर्म]] | name = सूरदास | image = Surdas, detail of a painting of Surdas with a devoted Brahmin, Kishangarh style, Rajasthan, circa 18th century (cropped).jpg | alt = Surdas | caption = सूरदास के एगो पेंटिंग, किशनगढ़ स्टाइल, राजस्थान, लगभग 18वीं सदी | birth_date = {{circa|1483}} | birth_place = सीही गाँव, [[फरीदाबाद]], [[दिल्ली सल्तनत]] | death_date = {{circa|1563}} | death_place = [[ब्रज]] परसौली, [[मुगल राज]] | known_for = [[भक्ति आंदोलन]] के अगुआ, [[संत मत]], | literary_works = ''[[सूरसागर]]'', ''सूर सारावली'', ''साहित्य लहरी'' | philosophy = [[भक्ति]] | father = रामदास सारस्वत<ref name="knowledgeocean.in">{{Cite web|url=https://knowledgeocean.in/biography-of-surdas-in-hindi-jivan-parichay-%E0%A4%B8%E0%A5%82%E0%A4%B0%E0%A4%A6%E0%A4%BE%E0%A4%B8-%E0%A4%95%E0%A4%BE-%E0%A4%9C%E0%A5%80%E0%A4%B5%E0%A4%A8-%E0%A4%AA%E0%A4%B0%E0%A4%BF%E0%A4%9A%E0%A4%AF/3290/|archive-url=https://web.archive.org/web/20200921003713/https://knowledgeocean.in/biography-of-surdas-in-hindi-jivan-parichay-%E0%A4%B8%E0%A5%82%E0%A4%B0%E0%A4%A6%E0%A4%BE%E0%A4%B8-%E0%A4%95%E0%A4%BE-%E0%A4%9C%E0%A5%80%E0%A4%B5%E0%A4%A8-%E0%A4%AA%E0%A4%B0%E0%A4%BF%E0%A4%9A%E0%A4%AF/3290/|url-status=usurped|archive-date=21 September 2020|title = सूरदास का जीवन परिचय - Biography of Surdas in Hindi Jivan Parichay| date=16 September 2020 }}</ref> | mother = जमुनादास<ref name="knowledgeocean.in"/> }} '''सूरदास''' 16वीं सदी के प्रसिद्ध [[भक्त]] [[कवि]] आ गायक रहलें।<ref name=Klaustermaier>{{cite book|author=Klaus K. Klostermaier|title=A Survey of Hinduism: Third Edition| url=https://books.google.com/books?id=E_6-JbUiHB4C&pg=PA215| date=2007-07-05| publisher=SUNY Press| isbn=978-0-7914-7082-4|page=215}}</ref> सूर नाँव पड़े के कारन उनुकर आँख से आन्हर भइल रहे। ऊ खास तौर पर [[कृष्ण]] के भक्ति में लिखल अपना पद आ [[भजन]] सभ खातिर जानल जालें। उनकर अधिकतर भक्ति गीत [[ब्रजभाषा]] में रचल गइल हवें, जबकि कुछ रचना मध्यकालीन हिंदी के अउरी दूसर बोली, जइसे [[अवधी भाषा|अवधी]], में भी मिले लीं।<ref name=":0">{{Cite web |date=2018-06-17 |title=Surdas Biography - Surdas Poems - Life History in English |url=https://indiathedestiny.com/icons/poets-writers/surdas-biography/ |access-date=2022-04-26 |website=India the Destiny |language=en-US |archive-date=26 June 2022 |archive-url=https://web.archive.org/web/20220626144617/https://indiathedestiny.com/icons/poets-writers/surdas-biography/ |url-status=dead }}</ref> सूरदास के जिनगी के कथा के वर्णन अक्सर पुष्टिमार्ग (वल्लभ संप्रदाय) के परंपरा के आधार पर कइल जाला। पुष्टिमार्ग के अनुसार सूरदास, [[वल्लभाचार्य]] के दीक्षित चेला रहलें। उनकर जीवन कथा ''[[चौरासी वैष्णवन की वार्ता]]'' ग्रंथ में मिलेला। सूरदास के पद, [[अष्टछाप]] के अउरी बाकी कवियन के रचना सभ के साथे, पुष्टिमार्ग के भजन-कीर्तन परंपरा के प्रमुख हिस्सा मानल जालें। हालाँकि, आधुनिक बिद्वान लोग सूरदास आ वल्लभाचार्य के बीच एह संबंध के ऐतिहासिक रूप से प्रमाणित ना मानेलें।<ref name=":02">{{Cite book |last=Hawley |first=John Stratton |title=Brill's Encyclopedia of Hinduism Online |publisher=Brill |year=2018 |editor-last=Jacobsen |editor-first=Knut A. |chapter=Sūrdās |editor-last2=Basu |editor-first2=Helene |editor-last3=Malinar |editor-first3=Angelika |editor-last4=Narayanan |editor-first4=Vasudha}}</ref> ''[[सूरसागर]]'' के परंपरागत रूप से सूरदास के रचल सभसे प्रमुख ग्रंथ मानल जाला। हालाँकि, विद्वान लोग के मान्यता ई बा कि एह ग्रंथ में शामिल कई पद बाद के कवियन द्वारा सूरदास के नाम से लिखल गइल हो सके लें। वर्तमान रूप में सूरसागर मुख्य रूप से गोकुल आ ब्रज के बालक कृष्ण के मनोहर लीला सभ के वर्णन करेला। एह पदन में अक्सर गोपी लोग के दृष्टिकोण से कृष्ण के बाल रूप आ उनकर प्रेममय लीला सभ के चित्रण मिलेला। == जिनगी आ कबितई == सूरदास के जन्म के बारे में अलग-अलग मत मिलेला। Encyclopaedia of Indian Literature के अनुसार उनकर जन्म 1258 ईस्वी में उत्तर प्रदेश के एगो ब्राह्मण परिवार में भइल रहे। जबकि Encyclopaedia Britannica में उनकर जीवनकाल परंपरागत रूप से 1483 से 1563 ईस्वी बतावल गइल बा। कुछ स्रोत उनकर संबंध सारस्वत ब्राह्मण, जाट भा ढाढ़ी समुदाय से जोड़ेलें। सूरदास के नाम के अर्थ "सूर्य के सेवक" होला। ब्रज भाषा के काव्य परंपरा में उनका के सर्वोच्च कवियन में गिनल जाला। ब्रज भाषा के संबंध व्रज क्षेत्र से बा, जहाँ हिन्दू परंपरा के अनुसार कृष्ण आपन बचपन बितवले रहलें। भक्त कवियन के जीवनचरित लिखे वाला नाभादास अपना ग्रंथ भक्तमाल में सूरदास के काव्य प्रतिभा के खूब प्रशंसा कइले बाड़ें। खास तौर पर ऊ कृष्ण के बाल लीला आ दिव्य क्रीड़ा के चित्रण में सूरदास के असाधारण कौशल के सराहना करेलें। सूरदास राम आ सीता पर भी कुछ पद लिखले रहलें, बाकिर उनकर अधिकांश रचना कृष्ण के जीवन, चरित्र आ लीलन पर केंद्रित बा। === कबितई === सूरदास के कविता के रचना मुख्य रूप से [[ब्रजभाषा]] में बाड़ी स। ओह जमाना में साहित्य के प्रमुख भाषा [[संस्कृत]] आ [[फ़ारसी]] मानल जालीं, जबकि ब्रज भाषा के आम लोग के बोली समझल जाय। सूरदास के रचना सभ ब्रजभाषा के साहित्यिक प्रतिष्ठा दियावे में महत्त्वपूर्ण भूमिका निभवलीं स। उनकर काव्य के कारण ब्रजभाषा के दर्जा एगो साधारण बोली से बढ़ के साहित्य के समृद्ध भाषा के रूप में स्थापित भइल। सूरदास के नाम से जुड़ल कविता सभ के एकट्ठा रूप से ''[[सूरसागर]]'' कहल जाला, जेकर अर्थ "सूर के सागर" होला। एह नाम के कारण ई बा कि उनका नाम से बहुत बड़हन संख्या में पद आ कविता सभ जुड़ल बाड़ी स। परंपरागत रूप से सूरसागर के बारह भाग में बाँटल जाला, जवन संरचना में [[श्रीमद्भागवत|भागवत पुराण]] से मिलत-जुलत बा। जइसे भागवत पुराण में कृष्ण के जीवन आ लीला सभ के बर्णन मिले ला , ओइसहीं सूरसागर के अधिकांश पद कृष्ण के जीवन, बाल लीला आ भक्ति पर केंद्रित बाड़ें। एह ग्रंथ में मिलल बहुत पद छह से दस गो तुकांत लाइन वाला गीतात्मक रचना के रूप में लिखल गइल हवें। कृष्ण के अलावा सूरदास के काव्य में राम आ सीता, विष्णु, शिव, आ हिन्दू परंपरा के पात्र जइसे गजेंद्र आ राजा बलि के भी वर्णन मिलेला। एकरे साथे उनका रचना में कवि के निजी आध्यात्मिक संघर्ष, भक्ति भावना आ ईश्वर से आत्मिक जुड़ाव के अभिव्यक्ति भी देखे के मिलेला। {{clear}} == संदर्भ == {{Reflist|29em}} [[श्रेणी:हिंदी-भाषा के कवि]] [[श्रेणी:भक्ति आंदोलन]] {{poet-stub}} tb3fqtpkxoj5k6yxvqj29b1nfq1d3bc 797569 797568 2026-06-09T01:24:39Z SM7 3953 अंग्रेजी विकिपीडिया से अनुबाद क के बिस्तार कइल गइल 797569 wikitext text/x-wiki {{Infobox religious biography | religion = [[हिंदू धर्म]] | name = सूरदास | image = Surdas, detail of a painting of Surdas with a devoted Brahmin, Kishangarh style, Rajasthan, circa 18th century (cropped).jpg | alt = Surdas | caption = सूरदास के एगो पेंटिंग, किशनगढ़ स्टाइल, राजस्थान, लगभग 18वीं सदी | birth_date = {{circa|1483}} | birth_place = सीही गाँव, [[फरीदाबाद]], [[दिल्ली सल्तनत]] | death_date = {{circa|1563}} | death_place = [[ब्रज]] परसौली, [[मुगल राज]] | known_for = [[भक्ति आंदोलन]] के अगुआ, [[संत मत]], | literary_works = ''[[सूरसागर]]'', ''सूर सारावली'', ''साहित्य लहरी'' | philosophy = [[भक्ति]] | father = रामदास सारस्वत<ref name="knowledgeocean.in">{{Cite web|url=https://knowledgeocean.in/biography-of-surdas-in-hindi-jivan-parichay-%E0%A4%B8%E0%A5%82%E0%A4%B0%E0%A4%A6%E0%A4%BE%E0%A4%B8-%E0%A4%95%E0%A4%BE-%E0%A4%9C%E0%A5%80%E0%A4%B5%E0%A4%A8-%E0%A4%AA%E0%A4%B0%E0%A4%BF%E0%A4%9A%E0%A4%AF/3290/|archive-url=https://web.archive.org/web/20200921003713/https://knowledgeocean.in/biography-of-surdas-in-hindi-jivan-parichay-%E0%A4%B8%E0%A5%82%E0%A4%B0%E0%A4%A6%E0%A4%BE%E0%A4%B8-%E0%A4%95%E0%A4%BE-%E0%A4%9C%E0%A5%80%E0%A4%B5%E0%A4%A8-%E0%A4%AA%E0%A4%B0%E0%A4%BF%E0%A4%9A%E0%A4%AF/3290/|url-status=usurped|archive-date=21 September 2020|title = सूरदास का जीवन परिचय - Biography of Surdas in Hindi Jivan Parichay| date=16 September 2020 }}</ref> | mother = जमुनादास<ref name="knowledgeocean.in"/> }} '''सूरदास''' 16वीं सदी के प्रसिद्ध [[भक्त]] [[कवि]] आ गायक रहलें।<ref name=Klaustermaier>{{cite book|author=Klaus K. Klostermaier|title=A Survey of Hinduism: Third Edition| url=https://books.google.com/books?id=E_6-JbUiHB4C&pg=PA215| date=2007-07-05| publisher=SUNY Press| isbn=978-0-7914-7082-4|page=215}}</ref> सूर नाँव पड़े के कारन उनुकर आँख से आन्हर भइल रहे। ऊ खास तौर पर [[कृष्ण]] के भक्ति में लिखल अपना पद आ [[भजन]] सभ खातिर जानल जालें। उनकर अधिकतर भक्ति गीत [[ब्रजभाषा]] में रचल गइल हवें, जबकि कुछ रचना मध्यकालीन हिंदी के अउरी दूसर बोली, जइसे [[अवधी भाषा|अवधी]], में भी मिले लीं।<ref name=":0">{{Cite web |date=2018-06-17 |title=Surdas Biography - Surdas Poems - Life History in English |url=https://indiathedestiny.com/icons/poets-writers/surdas-biography/ |access-date=2022-04-26 |website=India the Destiny |language=en-US |archive-date=26 June 2022 |archive-url=https://web.archive.org/web/20220626144617/https://indiathedestiny.com/icons/poets-writers/surdas-biography/ |url-status=dead }}</ref> सूरदास के जिनगी के कथा के वर्णन अक्सर पुष्टिमार्ग (वल्लभ संप्रदाय) के परंपरा के आधार पर कइल जाला। पुष्टिमार्ग के अनुसार सूरदास, [[वल्लभाचार्य]] के दीक्षित चेला रहलें। उनकर जीवन कथा ''[[चौरासी वैष्णवन की वार्ता]]'' ग्रंथ में मिलेला। सूरदास के पद, [[अष्टछाप]] के अउरी बाकी कवियन के रचना सभ के साथे, पुष्टिमार्ग के भजन-कीर्तन परंपरा के प्रमुख हिस्सा मानल जालें। हालाँकि, आधुनिक बिद्वान लोग सूरदास आ वल्लभाचार्य के बीच एह संबंध के ऐतिहासिक रूप से प्रमाणित ना मानेलें।<ref name=":02">{{Cite book |last=Hawley |first=John Stratton |title=Brill's Encyclopedia of Hinduism Online |publisher=Brill |year=2018 |editor-last=Jacobsen |editor-first=Knut A. |chapter=Sūrdās |editor-last2=Basu |editor-first2=Helene |editor-last3=Malinar |editor-first3=Angelika |editor-last4=Narayanan |editor-first4=Vasudha}}</ref> ''[[सूरसागर]]'' के परंपरागत रूप से सूरदास के रचल सभसे प्रमुख ग्रंथ मानल जाला। हालाँकि, विद्वान लोग के मान्यता ई बा कि एह ग्रंथ में शामिल कई पद बाद के कवियन द्वारा सूरदास के नाम से लिखल गइल हो सके लें। वर्तमान रूप में सूरसागर मुख्य रूप से गोकुल आ ब्रज के बालक कृष्ण के मनोहर लीला सभ के वर्णन करेला। एह पदन में अक्सर गोपी लोग के दृष्टिकोण से कृष्ण के बाल रूप आ उनकर प्रेममय लीला सभ के चित्रण मिलेला। == जिनगी आ कबितई == सूरदास के जन्म के बारे में अलग-अलग मत मिलेला। Encyclopaedia of Indian Literature के अनुसार उनकर जन्म 1258 ईस्वी में उत्तर प्रदेश के एगो ब्राह्मण परिवार में भइल रहे। जबकि Encyclopaedia Britannica में उनकर जीवनकाल परंपरागत रूप से 1483 से 1563 ईस्वी बतावल गइल बा। कुछ स्रोत उनकर संबंध सारस्वत ब्राह्मण, जाट भा ढाढ़ी समुदाय से जोड़ेलें। सूरदास के नाम के अर्थ "सूर्य के सेवक" होला। ब्रज भाषा के काव्य परंपरा में उनका के सर्वोच्च कवियन में गिनल जाला। ब्रज भाषा के संबंध व्रज क्षेत्र से बा, जहाँ हिन्दू परंपरा के अनुसार कृष्ण आपन बचपन बितवले रहलें। भक्त कवियन के जीवनचरित लिखे वाला नाभादास अपना ग्रंथ भक्तमाल में सूरदास के काव्य प्रतिभा के खूब प्रशंसा कइले बाड़ें। खास तौर पर ऊ कृष्ण के बाल लीला आ दिव्य क्रीड़ा के चित्रण में सूरदास के असाधारण कौशल के सराहना करेलें। सूरदास राम आ सीता पर भी कुछ पद लिखले रहलें, बाकिर उनकर अधिकांश रचना कृष्ण के जीवन, चरित्र आ लीलन पर केंद्रित बा। === कबितई === सूरदास के कविता के रचना मुख्य रूप से [[ब्रजभाषा]] में बाड़ी स। ओह जमाना में साहित्य के प्रमुख भाषा [[संस्कृत]] आ [[फ़ारसी]] मानल जालीं, जबकि ब्रज भाषा के आम लोग के बोली समझल जाय। सूरदास के रचना सभ ब्रजभाषा के साहित्यिक प्रतिष्ठा दियावे में महत्त्वपूर्ण भूमिका निभवलीं स। उनकर काव्य के कारण ब्रजभाषा के दर्जा एगो साधारण बोली से बढ़ के साहित्य के समृद्ध भाषा के रूप में स्थापित भइल। सूरदास के नाम से जुड़ल कविता सभ के एकट्ठा रूप से ''[[सूरसागर]]'' कहल जाला, जेकर अर्थ "सूर के सागर" होला। एह नाम के कारण ई बा कि उनका नाम से बहुत बड़हन संख्या में पद आ कविता सभ जुड़ल बाड़ी स। परंपरागत रूप से सूरसागर के बारह भाग में बाँटल जाला, जवन संरचना में [[श्रीमद्भागवत|भागवत पुराण]] से मिलत-जुलत बा। जइसे भागवत पुराण में कृष्ण के जीवन आ लीला सभ के बर्णन मिले ला , ओइसहीं सूरसागर के अधिकांश पद कृष्ण के जीवन, बाल लीला आ भक्ति पर केंद्रित बाड़ें। एह ग्रंथ में मिलल बहुत पद छह से दस गो तुकांत लाइन वाला गीतात्मक रचना के रूप में लिखल गइल हवें। कृष्ण के अलावा सूरदास के काव्य में राम आ सीता, विष्णु, शिव, आ हिन्दू परंपरा के पात्र जइसे गजेंद्र आ राजा बलि के भी वर्णन मिलेला। एकरे साथे उनका रचना में कवि के निजी आध्यात्मिक संघर्ष, भक्ति भावना आ ईश्वर से आत्मिक जुड़ाव के अभिव्यक्ति भी देखे के मिलेला। == दर्शन == [[वल्लभाचार्य]] के आठ प्रमुख शिष्यन के सामूहिक रूप से ''[[अष्टछाप]]'' कहल जाला। "अष्टछाप" के अर्थ "आठ गो मुहर" भा "आठ गो हस्ताक्षर" होला। एह नाम के संबंध ओह काव्य परंपरा से बा, जहाँ कवि अपना रचना के अंत में आपन छाप (काव्य-हस्ताक्षर) जोड़त रहलें। अष्टछाप के कवियन में सूरदास के सबसे प्रमुख आ श्रेष्ठ कवि मानल जाला। == पॉपुलर कल्चर में == सूरदास के जिनगी पर आधारित कई ठे फिलिम बन चुकल बाड़ी स। एहमें ''सूरदास'' (1939), ''भक्त सूरदास'' (1942), ''संत सूरदास'' (1975) आ ''चिंतामणि सूरदास'' (1988) प्रमुख बाड़ी स। आँख से आन्हर एगो दूसर भक्त कवि बिल्वमंगल (जिनका के कुछ परंपरन में सूरदास से जोड़ल जाला) आ चिंतामणि से जुड़ल लोककथा के भारतीय सिनेमा में कई बेर रूपांतरित कइल गइल बा। एह कथा पर आधारित प्रमुख फिल्मन में "बिल्वमंगल" भा "भगत सूरदास" (1919), "बिल्वमंगल" (1932), "चिंतामणि" (1933), "चिंतामणि" (1937), "भक्त बिल्वमंगल" (1948), "बिल्वमंगल" (1954), "भक्त बिल्वमंगल" (1954), "चिंतामणि" (1956), "चिंतामणि" (1957), "चिलम्बोली" (1963), "बिल्वमंगल" (1976) आ "विल्वमंगल की प्रतिज्ञा" (1996) शामिल बाड़ी स। एह फिल्मन में बिल्वमंगल के आध्यात्मिक परिवर्तन, भक्ति आ चिंतामणि से जुड़ल कथा के अलग-अलग रूप में प्रस्तुत कइल गइल बा। {{clear}} == संदर्भ == {{Reflist|29em}} [[श्रेणी:हिंदी-भाषा के कवि]] [[श्रेणी:भक्ति आंदोलन]] {{poet-stub}} fo361f31yn93h6klghb6kc7uwyc3bnl 797587 797569 2026-06-09T01:48:22Z SM7 3953 /* पॉपुलर कल्चर में */ सुधार कइल गइल 797587 wikitext text/x-wiki {{Infobox religious biography | religion = [[हिंदू धर्म]] | name = सूरदास | image = Surdas, detail of a painting of Surdas with a devoted Brahmin, Kishangarh style, Rajasthan, circa 18th century (cropped).jpg | alt = Surdas | caption = सूरदास के एगो पेंटिंग, किशनगढ़ स्टाइल, राजस्थान, लगभग 18वीं सदी | birth_date = {{circa|1483}} | birth_place = सीही गाँव, [[फरीदाबाद]], [[दिल्ली सल्तनत]] | death_date = {{circa|1563}} | death_place = [[ब्रज]] परसौली, [[मुगल राज]] | known_for = [[भक्ति आंदोलन]] के अगुआ, [[संत मत]], | literary_works = ''[[सूरसागर]]'', ''सूर सारावली'', ''साहित्य लहरी'' | philosophy = [[भक्ति]] | father = रामदास सारस्वत<ref name="knowledgeocean.in">{{Cite web|url=https://knowledgeocean.in/biography-of-surdas-in-hindi-jivan-parichay-%E0%A4%B8%E0%A5%82%E0%A4%B0%E0%A4%A6%E0%A4%BE%E0%A4%B8-%E0%A4%95%E0%A4%BE-%E0%A4%9C%E0%A5%80%E0%A4%B5%E0%A4%A8-%E0%A4%AA%E0%A4%B0%E0%A4%BF%E0%A4%9A%E0%A4%AF/3290/|archive-url=https://web.archive.org/web/20200921003713/https://knowledgeocean.in/biography-of-surdas-in-hindi-jivan-parichay-%E0%A4%B8%E0%A5%82%E0%A4%B0%E0%A4%A6%E0%A4%BE%E0%A4%B8-%E0%A4%95%E0%A4%BE-%E0%A4%9C%E0%A5%80%E0%A4%B5%E0%A4%A8-%E0%A4%AA%E0%A4%B0%E0%A4%BF%E0%A4%9A%E0%A4%AF/3290/|url-status=usurped|archive-date=21 September 2020|title = सूरदास का जीवन परिचय - Biography of Surdas in Hindi Jivan Parichay| date=16 September 2020 }}</ref> | mother = जमुनादास<ref name="knowledgeocean.in"/> }} '''सूरदास''' 16वीं सदी के प्रसिद्ध [[भक्त]] [[कवि]] आ गायक रहलें।<ref name=Klaustermaier>{{cite book|author=Klaus K. Klostermaier|title=A Survey of Hinduism: Third Edition| url=https://books.google.com/books?id=E_6-JbUiHB4C&pg=PA215| date=2007-07-05| publisher=SUNY Press| isbn=978-0-7914-7082-4|page=215}}</ref> सूर नाँव पड़े के कारन उनुकर आँख से आन्हर भइल रहे। ऊ खास तौर पर [[कृष्ण]] के भक्ति में लिखल अपना पद आ [[भजन]] सभ खातिर जानल जालें। उनकर अधिकतर भक्ति गीत [[ब्रजभाषा]] में रचल गइल हवें, जबकि कुछ रचना मध्यकालीन हिंदी के अउरी दूसर बोली, जइसे [[अवधी भाषा|अवधी]], में भी मिले लीं।<ref name=":0">{{Cite web |date=2018-06-17 |title=Surdas Biography - Surdas Poems - Life History in English |url=https://indiathedestiny.com/icons/poets-writers/surdas-biography/ |access-date=2022-04-26 |website=India the Destiny |language=en-US |archive-date=26 June 2022 |archive-url=https://web.archive.org/web/20220626144617/https://indiathedestiny.com/icons/poets-writers/surdas-biography/ |url-status=dead }}</ref> सूरदास के जिनगी के कथा के वर्णन अक्सर पुष्टिमार्ग (वल्लभ संप्रदाय) के परंपरा के आधार पर कइल जाला। पुष्टिमार्ग के अनुसार सूरदास, [[वल्लभाचार्य]] के दीक्षित चेला रहलें। उनकर जीवन कथा ''[[चौरासी वैष्णवन की वार्ता]]'' ग्रंथ में मिलेला। सूरदास के पद, [[अष्टछाप]] के अउरी बाकी कवियन के रचना सभ के साथे, पुष्टिमार्ग के भजन-कीर्तन परंपरा के प्रमुख हिस्सा मानल जालें। हालाँकि, आधुनिक बिद्वान लोग सूरदास आ वल्लभाचार्य के बीच एह संबंध के ऐतिहासिक रूप से प्रमाणित ना मानेलें।<ref name=":02">{{Cite book |last=Hawley |first=John Stratton |title=Brill's Encyclopedia of Hinduism Online |publisher=Brill |year=2018 |editor-last=Jacobsen |editor-first=Knut A. |chapter=Sūrdās |editor-last2=Basu |editor-first2=Helene |editor-last3=Malinar |editor-first3=Angelika |editor-last4=Narayanan |editor-first4=Vasudha}}</ref> ''[[सूरसागर]]'' के परंपरागत रूप से सूरदास के रचल सभसे प्रमुख ग्रंथ मानल जाला। हालाँकि, विद्वान लोग के मान्यता ई बा कि एह ग्रंथ में शामिल कई पद बाद के कवियन द्वारा सूरदास के नाम से लिखल गइल हो सके लें। वर्तमान रूप में सूरसागर मुख्य रूप से गोकुल आ ब्रज के बालक कृष्ण के मनोहर लीला सभ के वर्णन करेला। एह पदन में अक्सर गोपी लोग के दृष्टिकोण से कृष्ण के बाल रूप आ उनकर प्रेममय लीला सभ के चित्रण मिलेला। == जिनगी आ कबितई == सूरदास के जन्म के बारे में अलग-अलग मत मिलेला। Encyclopaedia of Indian Literature के अनुसार उनकर जन्म 1258 ईस्वी में उत्तर प्रदेश के एगो ब्राह्मण परिवार में भइल रहे। जबकि Encyclopaedia Britannica में उनकर जीवनकाल परंपरागत रूप से 1483 से 1563 ईस्वी बतावल गइल बा। कुछ स्रोत उनकर संबंध सारस्वत ब्राह्मण, जाट भा ढाढ़ी समुदाय से जोड़ेलें। सूरदास के नाम के अर्थ "सूर्य के सेवक" होला। ब्रज भाषा के काव्य परंपरा में उनका के सर्वोच्च कवियन में गिनल जाला। ब्रज भाषा के संबंध व्रज क्षेत्र से बा, जहाँ हिन्दू परंपरा के अनुसार कृष्ण आपन बचपन बितवले रहलें। भक्त कवियन के जीवनचरित लिखे वाला नाभादास अपना ग्रंथ भक्तमाल में सूरदास के काव्य प्रतिभा के खूब प्रशंसा कइले बाड़ें। खास तौर पर ऊ कृष्ण के बाल लीला आ दिव्य क्रीड़ा के चित्रण में सूरदास के असाधारण कौशल के सराहना करेलें। सूरदास राम आ सीता पर भी कुछ पद लिखले रहलें, बाकिर उनकर अधिकांश रचना कृष्ण के जीवन, चरित्र आ लीलन पर केंद्रित बा। === कबितई === सूरदास के कविता के रचना मुख्य रूप से [[ब्रजभाषा]] में बाड़ी स। ओह जमाना में साहित्य के प्रमुख भाषा [[संस्कृत]] आ [[फ़ारसी]] मानल जालीं, जबकि ब्रज भाषा के आम लोग के बोली समझल जाय। सूरदास के रचना सभ ब्रजभाषा के साहित्यिक प्रतिष्ठा दियावे में महत्त्वपूर्ण भूमिका निभवलीं स। उनकर काव्य के कारण ब्रजभाषा के दर्जा एगो साधारण बोली से बढ़ के साहित्य के समृद्ध भाषा के रूप में स्थापित भइल। सूरदास के नाम से जुड़ल कविता सभ के एकट्ठा रूप से ''[[सूरसागर]]'' कहल जाला, जेकर अर्थ "सूर के सागर" होला। एह नाम के कारण ई बा कि उनका नाम से बहुत बड़हन संख्या में पद आ कविता सभ जुड़ल बाड़ी स। परंपरागत रूप से सूरसागर के बारह भाग में बाँटल जाला, जवन संरचना में [[श्रीमद्भागवत|भागवत पुराण]] से मिलत-जुलत बा। जइसे भागवत पुराण में कृष्ण के जीवन आ लीला सभ के बर्णन मिले ला , ओइसहीं सूरसागर के अधिकांश पद कृष्ण के जीवन, बाल लीला आ भक्ति पर केंद्रित बाड़ें। एह ग्रंथ में मिलल बहुत पद छह से दस गो तुकांत लाइन वाला गीतात्मक रचना के रूप में लिखल गइल हवें। कृष्ण के अलावा सूरदास के काव्य में राम आ सीता, विष्णु, शिव, आ हिन्दू परंपरा के पात्र जइसे गजेंद्र आ राजा बलि के भी वर्णन मिलेला। एकरे साथे उनका रचना में कवि के निजी आध्यात्मिक संघर्ष, भक्ति भावना आ ईश्वर से आत्मिक जुड़ाव के अभिव्यक्ति भी देखे के मिलेला। == दर्शन == [[वल्लभाचार्य]] के आठ प्रमुख शिष्यन के सामूहिक रूप से ''[[अष्टछाप]]'' कहल जाला। "अष्टछाप" के अर्थ "आठ गो मुहर" भा "आठ गो हस्ताक्षर" होला। एह नाम के संबंध ओह काव्य परंपरा से बा, जहाँ कवि अपना रचना के अंत में आपन छाप (काव्य-हस्ताक्षर) जोड़त रहलें। अष्टछाप के कवियन में सूरदास के सबसे प्रमुख आ श्रेष्ठ कवि मानल जाला। == पॉपुलर कल्चर में == सूरदास के जिनगी पर आधारित कई ठे फिलिम बन चुकल बाड़ी स। एहमें ''सूरदास'' (1939), ''भक्त सूरदास'' (1942), ''संत सूरदास'' (1975) आ ''चिंतामणि सूरदास'' (1988) प्रमुख बाड़ी स। आँख से आन्हर एगो दूसर भक्त कवि बिल्वमंगल (जिनका के कुछ परंपरन में सूरदास से जोड़ल जाला) आ चिंतामणि के कथा के भारतीय सिनेमा में कई बेर रूपांतरित कइल गइल बा। एह कथा पर आधारित फिल्मन में "बिल्वमंगल" भा "भगत सूरदास" (1919), जेकर निर्देशन रुस्तमजी धोतीवाला कइले रहलें; "बिल्वमंगल" (1932); "चिंतामणि" (1933), जेकर निर्देशन कल्लाकुरी सदाशिव राव कइले रहलें; "चिंतामणि" (1937), जेकर निर्देशन वाई. वी. राव कइले रहलें; "भक्त बिल्वमंगल" (1948), जेकर निर्देशन शांति कुमार कइले रहलें; "बिल्वमंगल" (1954), जेकर निर्देशन डी. एन. माधोक कइले रहलें; "भक्त बिल्वमंगल" (1954), जेकर निर्देशन पिनाकी भूषण मुखर्जी कइले रहलें; "चिंतामणि" (1956), जेकर निर्देशन पी. एस. रामकृष्ण राव कइले रहलें; "चिंतामणि" (1957), जेकर निर्देशन एम. एन. बसवराजैया कइले रहलें; "चिलम्बोली" (1963), जेकर निर्देशन जी. के. रामू कइले रहलें; "बिल्वमंगल" (1976), जेकर निर्देशन गोबिंद राय कइले रहलें; आ "विल्वमंगल की प्रतिज्ञा" (1996), जेकर निर्देशन संजय विरमानी कइले रहलें, शामिल बाड़ी स। {{clear}} == संदर्भ == {{Reflist|29em}} [[श्रेणी:हिंदी-भाषा के कवि]] [[श्रेणी:भक्ति आंदोलन]] {{poet-stub}} pbohj60wx9jaaxksa6h0q466shetzaz 797588 797587 2026-06-09T01:48:41Z SM7 3953 [[विकिपीडिया:हॉट-कैट|हॉट-कैट]] द्वारा [[श्रेणी:संत मत]] जोड़ल गइल 797588 wikitext text/x-wiki {{Infobox religious biography | religion = [[हिंदू धर्म]] | name = सूरदास | image = Surdas, detail of a painting of Surdas with a devoted Brahmin, Kishangarh style, Rajasthan, circa 18th century (cropped).jpg | alt = Surdas | caption = सूरदास के एगो पेंटिंग, किशनगढ़ स्टाइल, राजस्थान, लगभग 18वीं सदी | birth_date = {{circa|1483}} | birth_place = सीही गाँव, [[फरीदाबाद]], [[दिल्ली सल्तनत]] | death_date = {{circa|1563}} | death_place = [[ब्रज]] परसौली, [[मुगल राज]] | known_for = [[भक्ति आंदोलन]] के अगुआ, [[संत मत]], | literary_works = ''[[सूरसागर]]'', ''सूर सारावली'', ''साहित्य लहरी'' | philosophy = [[भक्ति]] | father = रामदास सारस्वत<ref name="knowledgeocean.in">{{Cite web|url=https://knowledgeocean.in/biography-of-surdas-in-hindi-jivan-parichay-%E0%A4%B8%E0%A5%82%E0%A4%B0%E0%A4%A6%E0%A4%BE%E0%A4%B8-%E0%A4%95%E0%A4%BE-%E0%A4%9C%E0%A5%80%E0%A4%B5%E0%A4%A8-%E0%A4%AA%E0%A4%B0%E0%A4%BF%E0%A4%9A%E0%A4%AF/3290/|archive-url=https://web.archive.org/web/20200921003713/https://knowledgeocean.in/biography-of-surdas-in-hindi-jivan-parichay-%E0%A4%B8%E0%A5%82%E0%A4%B0%E0%A4%A6%E0%A4%BE%E0%A4%B8-%E0%A4%95%E0%A4%BE-%E0%A4%9C%E0%A5%80%E0%A4%B5%E0%A4%A8-%E0%A4%AA%E0%A4%B0%E0%A4%BF%E0%A4%9A%E0%A4%AF/3290/|url-status=usurped|archive-date=21 September 2020|title = सूरदास का जीवन परिचय - Biography of Surdas in Hindi Jivan Parichay| date=16 September 2020 }}</ref> | mother = जमुनादास<ref name="knowledgeocean.in"/> }} '''सूरदास''' 16वीं सदी के प्रसिद्ध [[भक्त]] [[कवि]] आ गायक रहलें।<ref name=Klaustermaier>{{cite book|author=Klaus K. Klostermaier|title=A Survey of Hinduism: Third Edition| url=https://books.google.com/books?id=E_6-JbUiHB4C&pg=PA215| date=2007-07-05| publisher=SUNY Press| isbn=978-0-7914-7082-4|page=215}}</ref> सूर नाँव पड़े के कारन उनुकर आँख से आन्हर भइल रहे। ऊ खास तौर पर [[कृष्ण]] के भक्ति में लिखल अपना पद आ [[भजन]] सभ खातिर जानल जालें। उनकर अधिकतर भक्ति गीत [[ब्रजभाषा]] में रचल गइल हवें, जबकि कुछ रचना मध्यकालीन हिंदी के अउरी दूसर बोली, जइसे [[अवधी भाषा|अवधी]], में भी मिले लीं।<ref name=":0">{{Cite web |date=2018-06-17 |title=Surdas Biography - Surdas Poems - Life History in English |url=https://indiathedestiny.com/icons/poets-writers/surdas-biography/ |access-date=2022-04-26 |website=India the Destiny |language=en-US |archive-date=26 June 2022 |archive-url=https://web.archive.org/web/20220626144617/https://indiathedestiny.com/icons/poets-writers/surdas-biography/ |url-status=dead }}</ref> सूरदास के जिनगी के कथा के वर्णन अक्सर पुष्टिमार्ग (वल्लभ संप्रदाय) के परंपरा के आधार पर कइल जाला। पुष्टिमार्ग के अनुसार सूरदास, [[वल्लभाचार्य]] के दीक्षित चेला रहलें। उनकर जीवन कथा ''[[चौरासी वैष्णवन की वार्ता]]'' ग्रंथ में मिलेला। सूरदास के पद, [[अष्टछाप]] के अउरी बाकी कवियन के रचना सभ के साथे, पुष्टिमार्ग के भजन-कीर्तन परंपरा के प्रमुख हिस्सा मानल जालें। हालाँकि, आधुनिक बिद्वान लोग सूरदास आ वल्लभाचार्य के बीच एह संबंध के ऐतिहासिक रूप से प्रमाणित ना मानेलें।<ref name=":02">{{Cite book |last=Hawley |first=John Stratton |title=Brill's Encyclopedia of Hinduism Online |publisher=Brill |year=2018 |editor-last=Jacobsen |editor-first=Knut A. |chapter=Sūrdās |editor-last2=Basu |editor-first2=Helene |editor-last3=Malinar |editor-first3=Angelika |editor-last4=Narayanan |editor-first4=Vasudha}}</ref> ''[[सूरसागर]]'' के परंपरागत रूप से सूरदास के रचल सभसे प्रमुख ग्रंथ मानल जाला। हालाँकि, विद्वान लोग के मान्यता ई बा कि एह ग्रंथ में शामिल कई पद बाद के कवियन द्वारा सूरदास के नाम से लिखल गइल हो सके लें। वर्तमान रूप में सूरसागर मुख्य रूप से गोकुल आ ब्रज के बालक कृष्ण के मनोहर लीला सभ के वर्णन करेला। एह पदन में अक्सर गोपी लोग के दृष्टिकोण से कृष्ण के बाल रूप आ उनकर प्रेममय लीला सभ के चित्रण मिलेला। == जिनगी आ कबितई == सूरदास के जन्म के बारे में अलग-अलग मत मिलेला। Encyclopaedia of Indian Literature के अनुसार उनकर जन्म 1258 ईस्वी में उत्तर प्रदेश के एगो ब्राह्मण परिवार में भइल रहे। जबकि Encyclopaedia Britannica में उनकर जीवनकाल परंपरागत रूप से 1483 से 1563 ईस्वी बतावल गइल बा। कुछ स्रोत उनकर संबंध सारस्वत ब्राह्मण, जाट भा ढाढ़ी समुदाय से जोड़ेलें। सूरदास के नाम के अर्थ "सूर्य के सेवक" होला। ब्रज भाषा के काव्य परंपरा में उनका के सर्वोच्च कवियन में गिनल जाला। ब्रज भाषा के संबंध व्रज क्षेत्र से बा, जहाँ हिन्दू परंपरा के अनुसार कृष्ण आपन बचपन बितवले रहलें। भक्त कवियन के जीवनचरित लिखे वाला नाभादास अपना ग्रंथ भक्तमाल में सूरदास के काव्य प्रतिभा के खूब प्रशंसा कइले बाड़ें। खास तौर पर ऊ कृष्ण के बाल लीला आ दिव्य क्रीड़ा के चित्रण में सूरदास के असाधारण कौशल के सराहना करेलें। सूरदास राम आ सीता पर भी कुछ पद लिखले रहलें, बाकिर उनकर अधिकांश रचना कृष्ण के जीवन, चरित्र आ लीलन पर केंद्रित बा। === कबितई === सूरदास के कविता के रचना मुख्य रूप से [[ब्रजभाषा]] में बाड़ी स। ओह जमाना में साहित्य के प्रमुख भाषा [[संस्कृत]] आ [[फ़ारसी]] मानल जालीं, जबकि ब्रज भाषा के आम लोग के बोली समझल जाय। सूरदास के रचना सभ ब्रजभाषा के साहित्यिक प्रतिष्ठा दियावे में महत्त्वपूर्ण भूमिका निभवलीं स। उनकर काव्य के कारण ब्रजभाषा के दर्जा एगो साधारण बोली से बढ़ के साहित्य के समृद्ध भाषा के रूप में स्थापित भइल। सूरदास के नाम से जुड़ल कविता सभ के एकट्ठा रूप से ''[[सूरसागर]]'' कहल जाला, जेकर अर्थ "सूर के सागर" होला। एह नाम के कारण ई बा कि उनका नाम से बहुत बड़हन संख्या में पद आ कविता सभ जुड़ल बाड़ी स। परंपरागत रूप से सूरसागर के बारह भाग में बाँटल जाला, जवन संरचना में [[श्रीमद्भागवत|भागवत पुराण]] से मिलत-जुलत बा। जइसे भागवत पुराण में कृष्ण के जीवन आ लीला सभ के बर्णन मिले ला , ओइसहीं सूरसागर के अधिकांश पद कृष्ण के जीवन, बाल लीला आ भक्ति पर केंद्रित बाड़ें। एह ग्रंथ में मिलल बहुत पद छह से दस गो तुकांत लाइन वाला गीतात्मक रचना के रूप में लिखल गइल हवें। कृष्ण के अलावा सूरदास के काव्य में राम आ सीता, विष्णु, शिव, आ हिन्दू परंपरा के पात्र जइसे गजेंद्र आ राजा बलि के भी वर्णन मिलेला। एकरे साथे उनका रचना में कवि के निजी आध्यात्मिक संघर्ष, भक्ति भावना आ ईश्वर से आत्मिक जुड़ाव के अभिव्यक्ति भी देखे के मिलेला। == दर्शन == [[वल्लभाचार्य]] के आठ प्रमुख शिष्यन के सामूहिक रूप से ''[[अष्टछाप]]'' कहल जाला। "अष्टछाप" के अर्थ "आठ गो मुहर" भा "आठ गो हस्ताक्षर" होला। एह नाम के संबंध ओह काव्य परंपरा से बा, जहाँ कवि अपना रचना के अंत में आपन छाप (काव्य-हस्ताक्षर) जोड़त रहलें। अष्टछाप के कवियन में सूरदास के सबसे प्रमुख आ श्रेष्ठ कवि मानल जाला। == पॉपुलर कल्चर में == सूरदास के जिनगी पर आधारित कई ठे फिलिम बन चुकल बाड़ी स। एहमें ''सूरदास'' (1939), ''भक्त सूरदास'' (1942), ''संत सूरदास'' (1975) आ ''चिंतामणि सूरदास'' (1988) प्रमुख बाड़ी स। आँख से आन्हर एगो दूसर भक्त कवि बिल्वमंगल (जिनका के कुछ परंपरन में सूरदास से जोड़ल जाला) आ चिंतामणि के कथा के भारतीय सिनेमा में कई बेर रूपांतरित कइल गइल बा। एह कथा पर आधारित फिल्मन में "बिल्वमंगल" भा "भगत सूरदास" (1919), जेकर निर्देशन रुस्तमजी धोतीवाला कइले रहलें; "बिल्वमंगल" (1932); "चिंतामणि" (1933), जेकर निर्देशन कल्लाकुरी सदाशिव राव कइले रहलें; "चिंतामणि" (1937), जेकर निर्देशन वाई. वी. राव कइले रहलें; "भक्त बिल्वमंगल" (1948), जेकर निर्देशन शांति कुमार कइले रहलें; "बिल्वमंगल" (1954), जेकर निर्देशन डी. एन. माधोक कइले रहलें; "भक्त बिल्वमंगल" (1954), जेकर निर्देशन पिनाकी भूषण मुखर्जी कइले रहलें; "चिंतामणि" (1956), जेकर निर्देशन पी. एस. रामकृष्ण राव कइले रहलें; "चिंतामणि" (1957), जेकर निर्देशन एम. एन. बसवराजैया कइले रहलें; "चिलम्बोली" (1963), जेकर निर्देशन जी. के. रामू कइले रहलें; "बिल्वमंगल" (1976), जेकर निर्देशन गोबिंद राय कइले रहलें; आ "विल्वमंगल की प्रतिज्ञा" (1996), जेकर निर्देशन संजय विरमानी कइले रहलें, शामिल बाड़ी स। {{clear}} == संदर्भ == {{Reflist|29em}} [[श्रेणी:हिंदी-भाषा के कवि]] [[श्रेणी:भक्ति आंदोलन]] [[श्रेणी:संत मत]] {{poet-stub}} ikt0awtq23z547ado7b3tdmz04v21aq 797589 797588 2026-06-09T01:49:00Z SM7 3953 [[विकिपीडिया:हॉट-कैट|हॉट-कैट]] द्वारा [[श्रेणी:हिंदू संत]] जोड़ल गइल 797589 wikitext text/x-wiki {{Infobox religious biography | religion = [[हिंदू धर्म]] | name = सूरदास | image = Surdas, detail of a painting of Surdas with a devoted Brahmin, Kishangarh style, Rajasthan, circa 18th century (cropped).jpg | alt = Surdas | caption = सूरदास के एगो पेंटिंग, किशनगढ़ स्टाइल, राजस्थान, लगभग 18वीं सदी | birth_date = {{circa|1483}} | birth_place = सीही गाँव, [[फरीदाबाद]], [[दिल्ली सल्तनत]] | death_date = {{circa|1563}} | death_place = [[ब्रज]] परसौली, [[मुगल राज]] | known_for = [[भक्ति आंदोलन]] के अगुआ, [[संत मत]], | literary_works = ''[[सूरसागर]]'', ''सूर सारावली'', ''साहित्य लहरी'' | philosophy = [[भक्ति]] | father = रामदास सारस्वत<ref name="knowledgeocean.in">{{Cite web|url=https://knowledgeocean.in/biography-of-surdas-in-hindi-jivan-parichay-%E0%A4%B8%E0%A5%82%E0%A4%B0%E0%A4%A6%E0%A4%BE%E0%A4%B8-%E0%A4%95%E0%A4%BE-%E0%A4%9C%E0%A5%80%E0%A4%B5%E0%A4%A8-%E0%A4%AA%E0%A4%B0%E0%A4%BF%E0%A4%9A%E0%A4%AF/3290/|archive-url=https://web.archive.org/web/20200921003713/https://knowledgeocean.in/biography-of-surdas-in-hindi-jivan-parichay-%E0%A4%B8%E0%A5%82%E0%A4%B0%E0%A4%A6%E0%A4%BE%E0%A4%B8-%E0%A4%95%E0%A4%BE-%E0%A4%9C%E0%A5%80%E0%A4%B5%E0%A4%A8-%E0%A4%AA%E0%A4%B0%E0%A4%BF%E0%A4%9A%E0%A4%AF/3290/|url-status=usurped|archive-date=21 September 2020|title = सूरदास का जीवन परिचय - Biography of Surdas in Hindi Jivan Parichay| date=16 September 2020 }}</ref> | mother = जमुनादास<ref name="knowledgeocean.in"/> }} '''सूरदास''' 16वीं सदी के प्रसिद्ध [[भक्त]] [[कवि]] आ गायक रहलें।<ref name=Klaustermaier>{{cite book|author=Klaus K. Klostermaier|title=A Survey of Hinduism: Third Edition| url=https://books.google.com/books?id=E_6-JbUiHB4C&pg=PA215| date=2007-07-05| publisher=SUNY Press| isbn=978-0-7914-7082-4|page=215}}</ref> सूर नाँव पड़े के कारन उनुकर आँख से आन्हर भइल रहे। ऊ खास तौर पर [[कृष्ण]] के भक्ति में लिखल अपना पद आ [[भजन]] सभ खातिर जानल जालें। उनकर अधिकतर भक्ति गीत [[ब्रजभाषा]] में रचल गइल हवें, जबकि कुछ रचना मध्यकालीन हिंदी के अउरी दूसर बोली, जइसे [[अवधी भाषा|अवधी]], में भी मिले लीं।<ref name=":0">{{Cite web |date=2018-06-17 |title=Surdas Biography - Surdas Poems - Life History in English |url=https://indiathedestiny.com/icons/poets-writers/surdas-biography/ |access-date=2022-04-26 |website=India the Destiny |language=en-US |archive-date=26 June 2022 |archive-url=https://web.archive.org/web/20220626144617/https://indiathedestiny.com/icons/poets-writers/surdas-biography/ |url-status=dead }}</ref> सूरदास के जिनगी के कथा के वर्णन अक्सर पुष्टिमार्ग (वल्लभ संप्रदाय) के परंपरा के आधार पर कइल जाला। पुष्टिमार्ग के अनुसार सूरदास, [[वल्लभाचार्य]] के दीक्षित चेला रहलें। उनकर जीवन कथा ''[[चौरासी वैष्णवन की वार्ता]]'' ग्रंथ में मिलेला। सूरदास के पद, [[अष्टछाप]] के अउरी बाकी कवियन के रचना सभ के साथे, पुष्टिमार्ग के भजन-कीर्तन परंपरा के प्रमुख हिस्सा मानल जालें। हालाँकि, आधुनिक बिद्वान लोग सूरदास आ वल्लभाचार्य के बीच एह संबंध के ऐतिहासिक रूप से प्रमाणित ना मानेलें।<ref name=":02">{{Cite book |last=Hawley |first=John Stratton |title=Brill's Encyclopedia of Hinduism Online |publisher=Brill |year=2018 |editor-last=Jacobsen |editor-first=Knut A. |chapter=Sūrdās |editor-last2=Basu |editor-first2=Helene |editor-last3=Malinar |editor-first3=Angelika |editor-last4=Narayanan |editor-first4=Vasudha}}</ref> ''[[सूरसागर]]'' के परंपरागत रूप से सूरदास के रचल सभसे प्रमुख ग्रंथ मानल जाला। हालाँकि, विद्वान लोग के मान्यता ई बा कि एह ग्रंथ में शामिल कई पद बाद के कवियन द्वारा सूरदास के नाम से लिखल गइल हो सके लें। वर्तमान रूप में सूरसागर मुख्य रूप से गोकुल आ ब्रज के बालक कृष्ण के मनोहर लीला सभ के वर्णन करेला। एह पदन में अक्सर गोपी लोग के दृष्टिकोण से कृष्ण के बाल रूप आ उनकर प्रेममय लीला सभ के चित्रण मिलेला। == जिनगी आ कबितई == सूरदास के जन्म के बारे में अलग-अलग मत मिलेला। Encyclopaedia of Indian Literature के अनुसार उनकर जन्म 1258 ईस्वी में उत्तर प्रदेश के एगो ब्राह्मण परिवार में भइल रहे। जबकि Encyclopaedia Britannica में उनकर जीवनकाल परंपरागत रूप से 1483 से 1563 ईस्वी बतावल गइल बा। कुछ स्रोत उनकर संबंध सारस्वत ब्राह्मण, जाट भा ढाढ़ी समुदाय से जोड़ेलें। सूरदास के नाम के अर्थ "सूर्य के सेवक" होला। ब्रज भाषा के काव्य परंपरा में उनका के सर्वोच्च कवियन में गिनल जाला। ब्रज भाषा के संबंध व्रज क्षेत्र से बा, जहाँ हिन्दू परंपरा के अनुसार कृष्ण आपन बचपन बितवले रहलें। भक्त कवियन के जीवनचरित लिखे वाला नाभादास अपना ग्रंथ भक्तमाल में सूरदास के काव्य प्रतिभा के खूब प्रशंसा कइले बाड़ें। खास तौर पर ऊ कृष्ण के बाल लीला आ दिव्य क्रीड़ा के चित्रण में सूरदास के असाधारण कौशल के सराहना करेलें। सूरदास राम आ सीता पर भी कुछ पद लिखले रहलें, बाकिर उनकर अधिकांश रचना कृष्ण के जीवन, चरित्र आ लीलन पर केंद्रित बा। === कबितई === सूरदास के कविता के रचना मुख्य रूप से [[ब्रजभाषा]] में बाड़ी स। ओह जमाना में साहित्य के प्रमुख भाषा [[संस्कृत]] आ [[फ़ारसी]] मानल जालीं, जबकि ब्रज भाषा के आम लोग के बोली समझल जाय। सूरदास के रचना सभ ब्रजभाषा के साहित्यिक प्रतिष्ठा दियावे में महत्त्वपूर्ण भूमिका निभवलीं स। उनकर काव्य के कारण ब्रजभाषा के दर्जा एगो साधारण बोली से बढ़ के साहित्य के समृद्ध भाषा के रूप में स्थापित भइल। सूरदास के नाम से जुड़ल कविता सभ के एकट्ठा रूप से ''[[सूरसागर]]'' कहल जाला, जेकर अर्थ "सूर के सागर" होला। एह नाम के कारण ई बा कि उनका नाम से बहुत बड़हन संख्या में पद आ कविता सभ जुड़ल बाड़ी स। परंपरागत रूप से सूरसागर के बारह भाग में बाँटल जाला, जवन संरचना में [[श्रीमद्भागवत|भागवत पुराण]] से मिलत-जुलत बा। जइसे भागवत पुराण में कृष्ण के जीवन आ लीला सभ के बर्णन मिले ला , ओइसहीं सूरसागर के अधिकांश पद कृष्ण के जीवन, बाल लीला आ भक्ति पर केंद्रित बाड़ें। एह ग्रंथ में मिलल बहुत पद छह से दस गो तुकांत लाइन वाला गीतात्मक रचना के रूप में लिखल गइल हवें। कृष्ण के अलावा सूरदास के काव्य में राम आ सीता, विष्णु, शिव, आ हिन्दू परंपरा के पात्र जइसे गजेंद्र आ राजा बलि के भी वर्णन मिलेला। एकरे साथे उनका रचना में कवि के निजी आध्यात्मिक संघर्ष, भक्ति भावना आ ईश्वर से आत्मिक जुड़ाव के अभिव्यक्ति भी देखे के मिलेला। == दर्शन == [[वल्लभाचार्य]] के आठ प्रमुख शिष्यन के सामूहिक रूप से ''[[अष्टछाप]]'' कहल जाला। "अष्टछाप" के अर्थ "आठ गो मुहर" भा "आठ गो हस्ताक्षर" होला। एह नाम के संबंध ओह काव्य परंपरा से बा, जहाँ कवि अपना रचना के अंत में आपन छाप (काव्य-हस्ताक्षर) जोड़त रहलें। अष्टछाप के कवियन में सूरदास के सबसे प्रमुख आ श्रेष्ठ कवि मानल जाला। == पॉपुलर कल्चर में == सूरदास के जिनगी पर आधारित कई ठे फिलिम बन चुकल बाड़ी स। एहमें ''सूरदास'' (1939), ''भक्त सूरदास'' (1942), ''संत सूरदास'' (1975) आ ''चिंतामणि सूरदास'' (1988) प्रमुख बाड़ी स। आँख से आन्हर एगो दूसर भक्त कवि बिल्वमंगल (जिनका के कुछ परंपरन में सूरदास से जोड़ल जाला) आ चिंतामणि के कथा के भारतीय सिनेमा में कई बेर रूपांतरित कइल गइल बा। एह कथा पर आधारित फिल्मन में "बिल्वमंगल" भा "भगत सूरदास" (1919), जेकर निर्देशन रुस्तमजी धोतीवाला कइले रहलें; "बिल्वमंगल" (1932); "चिंतामणि" (1933), जेकर निर्देशन कल्लाकुरी सदाशिव राव कइले रहलें; "चिंतामणि" (1937), जेकर निर्देशन वाई. वी. राव कइले रहलें; "भक्त बिल्वमंगल" (1948), जेकर निर्देशन शांति कुमार कइले रहलें; "बिल्वमंगल" (1954), जेकर निर्देशन डी. एन. माधोक कइले रहलें; "भक्त बिल्वमंगल" (1954), जेकर निर्देशन पिनाकी भूषण मुखर्जी कइले रहलें; "चिंतामणि" (1956), जेकर निर्देशन पी. एस. रामकृष्ण राव कइले रहलें; "चिंतामणि" (1957), जेकर निर्देशन एम. एन. बसवराजैया कइले रहलें; "चिलम्बोली" (1963), जेकर निर्देशन जी. के. रामू कइले रहलें; "बिल्वमंगल" (1976), जेकर निर्देशन गोबिंद राय कइले रहलें; आ "विल्वमंगल की प्रतिज्ञा" (1996), जेकर निर्देशन संजय विरमानी कइले रहलें, शामिल बाड़ी स। {{clear}} == संदर्भ == {{Reflist|29em}} [[श्रेणी:हिंदी-भाषा के कवि]] [[श्रेणी:भक्ति आंदोलन]] [[श्रेणी:संत मत]] [[श्रेणी:हिंदू संत]] {{poet-stub}} te938j7cti1ygjc89bw6kvb7i92rw2a 797590 797589 2026-06-09T01:49:18Z SM7 3953 [[विकिपीडिया:हॉट-कैट|हॉट-कैट]] द्वारा [[श्रेणी:वैष्णव संप्रदाय]] जोड़ल गइल 797590 wikitext text/x-wiki {{Infobox religious biography | religion = [[हिंदू धर्म]] | name = सूरदास | image = Surdas, detail of a painting of Surdas with a devoted Brahmin, Kishangarh style, Rajasthan, circa 18th century (cropped).jpg | alt = Surdas | caption = सूरदास के एगो पेंटिंग, किशनगढ़ स्टाइल, राजस्थान, लगभग 18वीं सदी | birth_date = {{circa|1483}} | birth_place = सीही गाँव, [[फरीदाबाद]], [[दिल्ली सल्तनत]] | death_date = {{circa|1563}} | death_place = [[ब्रज]] परसौली, [[मुगल राज]] | known_for = [[भक्ति आंदोलन]] के अगुआ, [[संत मत]], | literary_works = ''[[सूरसागर]]'', ''सूर सारावली'', ''साहित्य लहरी'' | philosophy = [[भक्ति]] | father = रामदास सारस्वत<ref name="knowledgeocean.in">{{Cite web|url=https://knowledgeocean.in/biography-of-surdas-in-hindi-jivan-parichay-%E0%A4%B8%E0%A5%82%E0%A4%B0%E0%A4%A6%E0%A4%BE%E0%A4%B8-%E0%A4%95%E0%A4%BE-%E0%A4%9C%E0%A5%80%E0%A4%B5%E0%A4%A8-%E0%A4%AA%E0%A4%B0%E0%A4%BF%E0%A4%9A%E0%A4%AF/3290/|archive-url=https://web.archive.org/web/20200921003713/https://knowledgeocean.in/biography-of-surdas-in-hindi-jivan-parichay-%E0%A4%B8%E0%A5%82%E0%A4%B0%E0%A4%A6%E0%A4%BE%E0%A4%B8-%E0%A4%95%E0%A4%BE-%E0%A4%9C%E0%A5%80%E0%A4%B5%E0%A4%A8-%E0%A4%AA%E0%A4%B0%E0%A4%BF%E0%A4%9A%E0%A4%AF/3290/|url-status=usurped|archive-date=21 September 2020|title = सूरदास का जीवन परिचय - Biography of Surdas in Hindi Jivan Parichay| date=16 September 2020 }}</ref> | mother = जमुनादास<ref name="knowledgeocean.in"/> }} '''सूरदास''' 16वीं सदी के प्रसिद्ध [[भक्त]] [[कवि]] आ गायक रहलें।<ref name=Klaustermaier>{{cite book|author=Klaus K. Klostermaier|title=A Survey of Hinduism: Third Edition| url=https://books.google.com/books?id=E_6-JbUiHB4C&pg=PA215| date=2007-07-05| publisher=SUNY Press| isbn=978-0-7914-7082-4|page=215}}</ref> सूर नाँव पड़े के कारन उनुकर आँख से आन्हर भइल रहे। ऊ खास तौर पर [[कृष्ण]] के भक्ति में लिखल अपना पद आ [[भजन]] सभ खातिर जानल जालें। उनकर अधिकतर भक्ति गीत [[ब्रजभाषा]] में रचल गइल हवें, जबकि कुछ रचना मध्यकालीन हिंदी के अउरी दूसर बोली, जइसे [[अवधी भाषा|अवधी]], में भी मिले लीं।<ref name=":0">{{Cite web |date=2018-06-17 |title=Surdas Biography - Surdas Poems - Life History in English |url=https://indiathedestiny.com/icons/poets-writers/surdas-biography/ |access-date=2022-04-26 |website=India the Destiny |language=en-US |archive-date=26 June 2022 |archive-url=https://web.archive.org/web/20220626144617/https://indiathedestiny.com/icons/poets-writers/surdas-biography/ |url-status=dead }}</ref> सूरदास के जिनगी के कथा के वर्णन अक्सर पुष्टिमार्ग (वल्लभ संप्रदाय) के परंपरा के आधार पर कइल जाला। पुष्टिमार्ग के अनुसार सूरदास, [[वल्लभाचार्य]] के दीक्षित चेला रहलें। उनकर जीवन कथा ''[[चौरासी वैष्णवन की वार्ता]]'' ग्रंथ में मिलेला। सूरदास के पद, [[अष्टछाप]] के अउरी बाकी कवियन के रचना सभ के साथे, पुष्टिमार्ग के भजन-कीर्तन परंपरा के प्रमुख हिस्सा मानल जालें। हालाँकि, आधुनिक बिद्वान लोग सूरदास आ वल्लभाचार्य के बीच एह संबंध के ऐतिहासिक रूप से प्रमाणित ना मानेलें।<ref name=":02">{{Cite book |last=Hawley |first=John Stratton |title=Brill's Encyclopedia of Hinduism Online |publisher=Brill |year=2018 |editor-last=Jacobsen |editor-first=Knut A. |chapter=Sūrdās |editor-last2=Basu |editor-first2=Helene |editor-last3=Malinar |editor-first3=Angelika |editor-last4=Narayanan |editor-first4=Vasudha}}</ref> ''[[सूरसागर]]'' के परंपरागत रूप से सूरदास के रचल सभसे प्रमुख ग्रंथ मानल जाला। हालाँकि, विद्वान लोग के मान्यता ई बा कि एह ग्रंथ में शामिल कई पद बाद के कवियन द्वारा सूरदास के नाम से लिखल गइल हो सके लें। वर्तमान रूप में सूरसागर मुख्य रूप से गोकुल आ ब्रज के बालक कृष्ण के मनोहर लीला सभ के वर्णन करेला। एह पदन में अक्सर गोपी लोग के दृष्टिकोण से कृष्ण के बाल रूप आ उनकर प्रेममय लीला सभ के चित्रण मिलेला। == जिनगी आ कबितई == सूरदास के जन्म के बारे में अलग-अलग मत मिलेला। Encyclopaedia of Indian Literature के अनुसार उनकर जन्म 1258 ईस्वी में उत्तर प्रदेश के एगो ब्राह्मण परिवार में भइल रहे। जबकि Encyclopaedia Britannica में उनकर जीवनकाल परंपरागत रूप से 1483 से 1563 ईस्वी बतावल गइल बा। कुछ स्रोत उनकर संबंध सारस्वत ब्राह्मण, जाट भा ढाढ़ी समुदाय से जोड़ेलें। सूरदास के नाम के अर्थ "सूर्य के सेवक" होला। ब्रज भाषा के काव्य परंपरा में उनका के सर्वोच्च कवियन में गिनल जाला। ब्रज भाषा के संबंध व्रज क्षेत्र से बा, जहाँ हिन्दू परंपरा के अनुसार कृष्ण आपन बचपन बितवले रहलें। भक्त कवियन के जीवनचरित लिखे वाला नाभादास अपना ग्रंथ भक्तमाल में सूरदास के काव्य प्रतिभा के खूब प्रशंसा कइले बाड़ें। खास तौर पर ऊ कृष्ण के बाल लीला आ दिव्य क्रीड़ा के चित्रण में सूरदास के असाधारण कौशल के सराहना करेलें। सूरदास राम आ सीता पर भी कुछ पद लिखले रहलें, बाकिर उनकर अधिकांश रचना कृष्ण के जीवन, चरित्र आ लीलन पर केंद्रित बा। === कबितई === सूरदास के कविता के रचना मुख्य रूप से [[ब्रजभाषा]] में बाड़ी स। ओह जमाना में साहित्य के प्रमुख भाषा [[संस्कृत]] आ [[फ़ारसी]] मानल जालीं, जबकि ब्रज भाषा के आम लोग के बोली समझल जाय। सूरदास के रचना सभ ब्रजभाषा के साहित्यिक प्रतिष्ठा दियावे में महत्त्वपूर्ण भूमिका निभवलीं स। उनकर काव्य के कारण ब्रजभाषा के दर्जा एगो साधारण बोली से बढ़ के साहित्य के समृद्ध भाषा के रूप में स्थापित भइल। सूरदास के नाम से जुड़ल कविता सभ के एकट्ठा रूप से ''[[सूरसागर]]'' कहल जाला, जेकर अर्थ "सूर के सागर" होला। एह नाम के कारण ई बा कि उनका नाम से बहुत बड़हन संख्या में पद आ कविता सभ जुड़ल बाड़ी स। परंपरागत रूप से सूरसागर के बारह भाग में बाँटल जाला, जवन संरचना में [[श्रीमद्भागवत|भागवत पुराण]] से मिलत-जुलत बा। जइसे भागवत पुराण में कृष्ण के जीवन आ लीला सभ के बर्णन मिले ला , ओइसहीं सूरसागर के अधिकांश पद कृष्ण के जीवन, बाल लीला आ भक्ति पर केंद्रित बाड़ें। एह ग्रंथ में मिलल बहुत पद छह से दस गो तुकांत लाइन वाला गीतात्मक रचना के रूप में लिखल गइल हवें। कृष्ण के अलावा सूरदास के काव्य में राम आ सीता, विष्णु, शिव, आ हिन्दू परंपरा के पात्र जइसे गजेंद्र आ राजा बलि के भी वर्णन मिलेला। एकरे साथे उनका रचना में कवि के निजी आध्यात्मिक संघर्ष, भक्ति भावना आ ईश्वर से आत्मिक जुड़ाव के अभिव्यक्ति भी देखे के मिलेला। == दर्शन == [[वल्लभाचार्य]] के आठ प्रमुख शिष्यन के सामूहिक रूप से ''[[अष्टछाप]]'' कहल जाला। "अष्टछाप" के अर्थ "आठ गो मुहर" भा "आठ गो हस्ताक्षर" होला। एह नाम के संबंध ओह काव्य परंपरा से बा, जहाँ कवि अपना रचना के अंत में आपन छाप (काव्य-हस्ताक्षर) जोड़त रहलें। अष्टछाप के कवियन में सूरदास के सबसे प्रमुख आ श्रेष्ठ कवि मानल जाला। == पॉपुलर कल्चर में == सूरदास के जिनगी पर आधारित कई ठे फिलिम बन चुकल बाड़ी स। एहमें ''सूरदास'' (1939), ''भक्त सूरदास'' (1942), ''संत सूरदास'' (1975) आ ''चिंतामणि सूरदास'' (1988) प्रमुख बाड़ी स। आँख से आन्हर एगो दूसर भक्त कवि बिल्वमंगल (जिनका के कुछ परंपरन में सूरदास से जोड़ल जाला) आ चिंतामणि के कथा के भारतीय सिनेमा में कई बेर रूपांतरित कइल गइल बा। एह कथा पर आधारित फिल्मन में "बिल्वमंगल" भा "भगत सूरदास" (1919), जेकर निर्देशन रुस्तमजी धोतीवाला कइले रहलें; "बिल्वमंगल" (1932); "चिंतामणि" (1933), जेकर निर्देशन कल्लाकुरी सदाशिव राव कइले रहलें; "चिंतामणि" (1937), जेकर निर्देशन वाई. वी. राव कइले रहलें; "भक्त बिल्वमंगल" (1948), जेकर निर्देशन शांति कुमार कइले रहलें; "बिल्वमंगल" (1954), जेकर निर्देशन डी. एन. माधोक कइले रहलें; "भक्त बिल्वमंगल" (1954), जेकर निर्देशन पिनाकी भूषण मुखर्जी कइले रहलें; "चिंतामणि" (1956), जेकर निर्देशन पी. एस. रामकृष्ण राव कइले रहलें; "चिंतामणि" (1957), जेकर निर्देशन एम. एन. बसवराजैया कइले रहलें; "चिलम्बोली" (1963), जेकर निर्देशन जी. के. रामू कइले रहलें; "बिल्वमंगल" (1976), जेकर निर्देशन गोबिंद राय कइले रहलें; आ "विल्वमंगल की प्रतिज्ञा" (1996), जेकर निर्देशन संजय विरमानी कइले रहलें, शामिल बाड़ी स। {{clear}} == संदर्भ == {{Reflist|29em}} [[श्रेणी:हिंदी-भाषा के कवि]] [[श्रेणी:भक्ति आंदोलन]] [[श्रेणी:संत मत]] [[श्रेणी:हिंदू संत]] [[श्रेणी:वैष्णव संप्रदाय]] {{poet-stub}} 85k21nij49acaaro7t1blwjsgfdy9ma टेम्पलेट:Infobox religious biography 10 53660 797586 778020 2026-06-09T01:39:12Z SM7 3953 अपडेट कइल गइल 797586 wikitext text/x-wiki {{Infobox|child={{if empty|{{{child|}}}|{{{embed|}}}}} | bodyclass = biography vcard | abovestyle = font-size:125%;background:{{if empty|{{{background|}}}|{{Infobox religious building/color|{{{religion|}}}}}}}; color:{{Greater color contrast ratio|{{if empty|{{{background|}}}|{{Infobox religious building/color|{{{religion|}}}}}}}}}; | above = {{#if:{{{honorific prefix|}}}{{{honorific_prefix|}}}{{{honorific-prefix|}}}{{{pre-nominals|}}}|<div class="honorific-prefix" style="font-size: 77%; font-weight: normal;">{{if empty|{{{honorific prefix|}}}|{{{honorific_prefix|}}}|{{{honorific-prefix|}}}|{{{pre-nominals|}}}}}</div>}}<div class="fn">{{if empty|{{{name|}}}|{{PAGENAMEBASE}}}}</div>{{#if:{{{honorific suffix|}}}{{{honorific_suffix|}}}{{{honorific-suffix|}}}{{{post-nominals|}}}|<div class="honorific-suffix" style="font-size: 77%; font-weight: normal;">{{if empty|{{{honorific suffix|}}}|{{{honorific_suffix|}}}|{{{honorific-suffix|}}}|{{{post-nominals|}}}}}</div>}} | subheaderstyle = font-size:125%; font-weight:bold; | subheader = {{#if:{{{native-name|}}}{{{native_name|}}}{{{native name|}}} |<div class="nickname" {{#if:{{{native-name-lang{{if empty|{{{native_name_lang|}}}|{{{native name lang|}}}}}}}}|lang="{{{native-name-lang{{if empty|{{{native_name_lang|}}}|{{{native name lang}}}}}}}}"}}>{{if empty|{{{native-name|}}}|{{{native_name|}}}|{{{native name}}}}}</div> }} | image = {{#invoke:InfoboxImage|InfoboxImage|image={{{image|}}}|size={{{image_size|}}}|sizedefault=frameless|upright={{if empty|{{{image_upright|}}}|1}}|alt={{{alt|}}}|suppressplaceholder=yes}} | caption = {{{caption|}}} | headerstyle = background:{{if empty|{{{background|}}}|{{Infobox religious building/color|{{{religion|}}}}}}}; color:{{Greater color contrast ratio|{{if empty|{{{background|}}}|{{Infobox religious building/color|{{{religion|}}}}}}}}}; | data1 = {{{module0|}}} | data2 = {{Infobox officeholder/office|color={{if empty|{{{background|}}}|{{Infobox religious building/color|{{{religion|}}}}}}} | office = {{{office1|}}} | term = {{{term1|}}} | termstart = {{{term_start1|}}} | termend = {{{term_end1|}}} | predecessor = {{{predecessor1|}}} | successor = {{{successor1|}}} }} {{Infobox officeholder/office|color={{if empty|{{{background|}}}|{{Infobox religious building/color|{{{religion|}}}}}}} | office = {{{office2|}}} | term = {{{term2|}}} | termstart = {{{term_start2|}}} | termend = {{{term_end2|}}} | predecessor = {{{predecessor2|}}} | successor = {{{successor2|}}} }} {{Infobox officeholder/office|color={{if empty|{{{background|}}}|{{Infobox religious building/color|{{{religion|}}}}}}} | office = {{{office3|}}} | term = {{{term3|}}} | termstart = {{{term_start3|}}} | termend = {{{term_end3|}}} | predecessor = {{{predecessor3|}}} | successor = {{{successor3|}}} }} {{Infobox officeholder/office|color={{if empty|{{{background|}}}|{{Infobox religious building/color|{{{religion|}}}}}}} | office = {{{office4|}}} | term = {{{term4|}}} | termstart = {{{term_start4|}}} | termend = {{{term_end4|}}} | predecessor = {{{predecessor4|}}} | successor = {{{successor4|}}} }} | label3 = Title | data3 = {{{title|}}} | label4 = Official name | data4 = {{{official_name|}}} | header6 = {{#if:{{{birth_name|}}}{{{birthname|}}}{{{birth_date|}}}{{{birth_place|}}}{{{nationality|}}}{{{flourished|}}}{{{home_town|}}}{{{partner|}}}{{{spouse|}}}{{{children|}}}{{{parents|}}}{{{citizenship|}}}{{{era|}}}{{{region|}}}{{{party|}}}{{{main interests|}}}{{{notable ideas|}}}{{{notable works|}}}{{{education|}}}{{{known_for|}}}{{{alias|}}}{{{pen_name|}}}{{{posthumous_name|}}}{{{occupation|}}}{{{relations|}}} |Personal life}} | label7 = Born | data7 = {{br separated entries |1={{#if:{{{birth_name|}}}{{{birthname|}}}|<span class="nickname">{{if empty|{{{birth_name|}}}|{{{birthname|}}}}}</span>}} |2={{#invoke:person date|birth}} |3={{{pronunciation|}}} |4={{#if:{{{birth_place|}}}|<div class="birthplace">{{{birth_place|}}}</div>}} }} | label8 = Died | data8 = {{br separated entries |1={{#invoke:person date|death}} |2= {{#if:{{{death_place|}}}|<div class="deathplace">{{{death_place|}}}</div>}} }} | label9 = Cause of death | data9 = {{{death_cause|}}} | label10 = {{#if:{{{cremation_place|}}}|Cremation place|Resting place}} | data10 = {{#if:{{{cremation_place|}}} |{{{cremation_place}}} |{{br separated entries |{{{resting_place|}}} |{{{resting_place_coordinates|}}} }} }} | label11 = Buried | data11 = {{br separated entries |{{if empty|{{{buried|}}}|{{{tomb|}}}|{{{burial_place|}}}}} |{{{burial_date|}}} }} | class12 = nickname | label12 = Nationality | data12 = {{{nationality|}}} | label13 = Flourished | data13 = {{{flourished|}}} | label14 = Home town | data14 = {{{home_town|}}} | label15 = {{#if:{{{partner|}}}|Partner|Spouse}} | data15 = {{if empty|{{{partner|}}}|{{{spouse|}}}}} | label16 = Children | data16 = {{{children|}}} | label17 = Parent{{#if:{{{parents|}}}|{{Pluralize from text|{{{parents|}}}|likely=(s)|plural=s}}|{{#ifexpr:{{count|{{{father|}}}|{{{mother|}}}}} > 1|s}}}} | data17 = {{#if:{{{parents|}}} |{{{parents|}}} |{{#if:{{{father|}}}{{{mother|}}} |{{unbulleted list|1={{#if:{{{father}}}|{{{father|}}} (father)}}|2={{#if:{{{mother|}}}|{{{mother|}}} (mother)}}}} }} }} | label18 = [[Citizenship]] | data18 = {{{citizenship|}}} | label20 = {{#if:{{{era|}}}|Era|Dynasty}} | data20 = {{if empty|{{{era|}}}|{{{dynasty|}}}}} | label21 = Region | data21 = {{{region|}}} | label29 = [[Political party]] | data29 = {{if empty|{{{political_party|}}}|{{{political party|}}}|{{{party|}}}}} | label30 = Main interest(s) | data30 = {{if empty|{{{main_interests|}}}|{{{main interests|}}}}} | label31 = Notable idea(s) | data31 = {{if empty|{{{ideas|}}}|{{{notable_ideas|}}}|{{{notable ideas|}}}}} | label32 = Notable work(s) | data32 = {{if empty|{{{works|}}}|{{{notable_works|}}}|{{{notable works|}}}}} | label33 = {{#if:{{{education|}}}|Education|[[Alma mater|Alma&nbsp;mater]]}} | data33 = {{if empty|{{{education|}}}|{{{alma_mater|}}}}} | label36 = Known&nbsp;for | data36 = {{{known_for|}}} | class37 = nickname | label37 = Other&nbsp;names | data37 = {{if empty|{{{other_names|}}}|{{{other_name|}}}|{{{alias|}}}}} | class40 = nickname | label40 = Pen&nbsp;name | data40 = {{{pen_name|}}} | class41 = nickname | label41 = Posthumous name | data41 = {{{posthumous_name|}}} | label43 = Occupation | data43 = {{{occupation|}}} | label44 = {{#if:{{{relations|}}}|Relations|Relatives}} | data44 = {{if empty|{{{relations|}}}|{{{relatives|}}}}} | data45 = {{{misc|}}} | label46 = {{#if:{{{honors|}}}|Honors|Honours}} | data46 = {{if empty|{{{honors|}}}|{{{honours|}}}}} | label47 = Signature | data47 = {{#invoke:InfoboxImage|InfoboxImage|image={{{signature|}}}|class=skin-invert-image|size={{{signature_size|}}}|sizedefault=150px|alt={{if empty|{{{signature alt|}}}|{{{signature_alt|}}}}}}} | header67 = {{#if:{{{religion|}}}{{{denomination|}}}{{{temple|}}}{{{order|}}}{{{institute|}}}{{{church|}}}{{{churches|}}}{{{founder|}}}{{{philosophy|}}}|Religious life}} | class68 = category | label68 = Religion | data68 = {{{religion|}}} | class69 = category | label69 = Denomination | data69 = {{{denomination|}}} | label70 = Temple | data70 = {{{temple|}}} | label71 = Order | data71 = {{{order|}}} | label72 = Institute | data72 = {{{institute|}}} | label73 = Church | data73 = {{if empty|{{{church|}}}|{{{churches|}}}}} | label74 = Founder&nbsp;of | data74 = {{{founder|}}} | label75 = Philosophy | data75 = {{{philosophy|}}} | class76 = category | label76 = School | data76 = {{{school|}}} | label77 = Lineage | data77 = {{{lineage|}}} | class78 = category | label78 = Sect | data78 = {{br separated entries |{{{sect|}}} |{{{subsect|}}} }} | class79 = category | label79 = Jurisprudence | data79 = {{if empty|{{{Madh'hab|}}}|{{{Maddhab|}}}|{{{jurisprudence|}}}}} | label80 = Teachers | data80 = {{{teachers|}}} | label81 = [[Tariqa]] | data81 = {{{Sufi_order|}}} | class82 = category | label82 = Creed | data82 = {{if empty|{{{school_tradition|}}}|{{{creed|}}}}} | label83 = [[Religious movement|Movement]] | data83 = {{{movement|}}} | class84 = nickname | label84 = [[Dharma&nbsp;name]]s | data84 = {{if empty|{{{dharma_names|}}}|{{{dharma_name|}}}}} | class85 = nickname | label85 = Monastic&nbsp;name | data85 = {{{monastic_name|}}} | label86 = Profession | data86 = {{{profession|}}} | label87 = Ordination | data87 = {{{ordination|}}} | label88 = Consecration | data88 = {{{consecration|}}} | label90 = Initiation{{#if:{{{initiation_rank|}}}|&nbsp;as {{{initiation_rank|}}}}} | data90 = {{br separated entries|{{{initiation|}}}|{{{initiation_name|}}}|{{{initiation_date|}}}|{{{initiation_place|}}}|{{#if:{{{initiator|}}}|by {{{initiator|}}}}}}} | label91 = Initiation{{#if:{{{initiation_rank2|}}}|&nbsp;as {{{initiation_rank2|}}}}} | data91 = {{br separated entries|{{{initiation_name2|}}}|{{{initiation_date2|}}}|{{{initiation_place2|}}}|{{#if:{{{initiator2|}}}|by {{{initiator2|}}}}}}} | header92 = {{#if:{{{teacher|}}}{{{guru|}}}{{{location|}}}{{{period|}}}{{{Period|}}}{{{consecration|}}}{{{predecessor|}}}{{{Predecessor|}}}{{{successor|}}}{{{Successor|}}}{{{reincarnation_of|}}}{{{reincarnation of|}}}{{{reason|}}}{{{Reason|}}}{{{disciple_of|}}} {{{disciples|}}}{{{students|}}}{{{influences|}}}{{{influenced|}}}{{{awards|}}}{{{literary_works|}}}{{{previous_post|}}}{{{previous post|}}}{{{present_post|}}}{{{present post|}}}{{{post|}}}|{{#switch:{{{religion|}}}|[[Hinduism]]|[[Jainism]]|[[Sikhism]]=Religious career|[[Judaism]]=Jewish leader|[[Islam]]=Muslim leader|#default=Senior posting}} }} | label93 = {{#if:{{{guru|}}}|Guru|Teacher}} | data93 = {{if empty|{{{guru|}}}|{{{teacher|}}}}} | class94 = label | label94 = Based&nbsp;in | data94 = {{{location|}}} | label95 = Present post | data95 = {{if empty|{{{present_post|}}}|{{{present post|}}}}} | label96 = Post | data96 = {{{post|}}} | label97 = Period in&nbsp;office | data97 = {{if empty|{{{period|}}}|{{{Period|}}}}} | label98 = Predecessor | data98 = {{if empty|{{{predecessor|}}}|{{{Predecessor|}}}}} | label99 = Successor | data99 = {{if empty|{{{successor|}}}|{{{Successor|}}}}} | label100 = Reason for&nbsp;exit | data100 = {{if empty|{{{reason|}}}|{{{Reason|}}}}} | label101 = Previous&nbsp;post | data101 = {{if empty|{{{previous_post|}}}|{{{previous post|}}}}} | label102 = [[Reincarnation]] | data102 = {{if empty|{{{reincarnation_of|}}}|{{{reincarnation of|}}}}} | label103 = Disciple of | data103 = {{{disciple_of|}}} | data104 = {{#if:{{{disciples|}}} | {{Collapsible list | expand = {{{expand_disciples|}}} | title = Disciples | frame_style = border:none; padding:0; | list_style = text-align:center; | 1 = {{{disciples}}} }} }} | data105 = {{#if:{{{students|}}} | {{Collapsible list | expand = {{{expand_students|}}} | title = Students | frame_style = border:none; padding:0; | list_style = text-align:center; | 1 = {{{students}}} }} }} | label106 = {{if empty|{{{initiated_label|}}}|Initiated}} | data106 = {{{initiated|}}} | data107 = {{#if:{{{influences|}}} | {{Collapsible list | expand = {{{expand_influences|}}} | title = Influenced&nbsp;by | frame_style = border:none; padding:0; | list_style = text-align:center; | 1 = {{{influences}}} }} }} | data108 = {{#if:{{{influenced|}}} | {{Collapsible list | expand = {{{expand_influenced|}}} | title = Influenced | frame_style = border:none; padding:0; | list_style = text-align:center; | 1 = {{{influenced}}} }} }} | label109 = Awards | data109 = {{{awards|}}} | label110 = Position | data110 = {{{synagogueposition|}}} | class110 = org | label111 = Synagogue | data111 = {{{synagogue|}}} | label112 = Position | data112 = {{{yeshivaposition|}}} | label113 = Yeshiva | data113 = {{{yeshiva|}}} | label114 = Position | data114 = {{if empty|{{{organizationposition|}}}|{{{organisationposition|}}}}} | class114 = label | label115 = {{#if:{{{organization|}}}|Organization|Organisation}} | data115 = {{if empty|{{{organization|}}}|{{{organisation|}}}}} | label116 = Began | data116 = {{{began|}}} | rowclass116 = note | label117 = Ended | data117 = {{{ended|}}} | rowclass117 = note | label118 = Main work | data118 = {{{main_work|}}} | label119 = Other | data119 = {{{other_post|}}} | label120 = ''[[Yahrtzeit]]'' | data120 = {{{yahrtzeit|}}} | label122 = Residence | data122 = {{{residence|}}} | class122 = label | label123 = Dynasty | data123 = {{{dynasty|}}} | label124 = [[Semikhah]] | data124 = {{{semicha|}}} | header129 = {{#if:{{{nickname|}}}{{{allegiance|}}}{{{branch|}}}{{{serviceyears|}}}{{{rank|}}}{{{unit|}}}{{{commands|}}}{{{battles|}}}{{{mawards|}}}{{{military_blank1|}}}|Military service}} | label130 = Nickname(s) | data130 = {{{nickname|}}} | label131 = Allegiance | data131 = {{{allegiance|}}} | label132 = Service/branch | data132 = {{{branch|}}} | label133 = Years of service | data133 = {{{serviceyears|}}} | label134 = Rank | data134 = {{if empty|{{{rank|}}}|{{{Rank|}}}}} | label135 = Unit | data135 = {{{unit|}}} | label136 = Commands | data136 = {{{commands|}}} | label137 = Battles/wars | data137 = {{{battles|}}} | label138 = {{#if:{{{awards|}}}|Military awards|Awards}} | data138 = {{{mawards|}}} | label139 = {{{military_blank1}}} | data139 = {{{military_data1|}}} | label140 = {{{military_blank2}}} | data140 = {{{military_data2|}}} | label141 = {{{military_blank3}}} | data141 = {{{military_data3|}}} | label142 = {{{military_blank4}}} | data142 = {{{military_data4|}}} | label143 = {{{military_blank5}}} | data143 = {{{military_data5|}}} | data144 = {{{module|}}} | data145 = {{{module1|}}} | data146 = {{{module2|}}} | data147 = {{{module3|}}} | data148 = {{{module4|}}} | data149 = {{{module5|}}} | label159 = Website | data159 = {{{website|}}} | belowstyle = text-align:left; border-top:1px #aaa solid; | below = {{{footnotes|}}} }}{{#if:{{{dharma_names|}}}{{{dharma_name|}}}{{{dharma name|}}}{{{monastic_name|}}}{{{monastic name|}}}{{{posthumous_name|}}}{{{posthumous name|}}}{{{pen_name|}}}{{{pen name|}}}|[[Category:Pages using religious biography with multiple nickname parameters]]}}<includeonly>{{#ifeq:{{if empty|{{{child|}}}|{{{embed|}}}}}|yes||{{Wikidata image|1={{{image|}}}|2={{{nocat_wdimage|}}}}}}}</includeonly>{{#invoke:Check for unknown parameters|check|unknown={{main other|[[Category:Pages using infobox religious biography with unknown parameters|_VALUE_{{PAGENAME}}]]}}|preview=Page using [[Template:Infobox religious biography]] with unknown parameter "_VALUE_"|ignoreblank=y| alias | allegiance | alma_mater | alt | awards | background | battles | began | birth_date | birth_name | birth_place | birthname | branch | burial_date | burial_place | buried | caption | child | children | church | churches | citizenship | commands | consecration | creed | cremation_place | death_cause | death_date | death_place | denomination | dharma name | dharma_name | dharma_names | disciple_of | disciples | dynasty | education | embed | ended | era | expand_disciples | expand_influenced | expand_influences | expand_students | father | flourished | footnotes | founder | guru | home_town | honorific prefix | honorific suffix | honorific_prefix | honorific_suffix | honorific-prefix | honorific-suffix | honors | honours | ideas | image | image_size | image_upright | influenced | influences | initiated | initiated_label | initiation | initiation_date | initiation_date2 | initiation_name | initiation_name2 | initiation_place | initiation_place2 | initiation_rank | initiation_rank2 | initiator | initiator2 | institute | jurisprudence | known_for | lineage | literary_works | location | Maddhab | Madh'hab | main interests | main_interests | main_work | mawards | military_blank1 | military_blank2 | military_blank3 | military_blank4 | military_blank5 | military_data1 | military_data2 | military_data3 | military_data4 | military_data5 | misc | module | module0 | module1 | module2 | module3 | module4 | module5 | monastic name | monastic_name | mother | movement | name | nationality | native name | native name lang | native_name | native_name_lang | native-name | native-name-lang | nickname | nocat_wdimage | notable ideas | notable works | notable_ideas | notable_works | occupation | office1 | office2 | office3 | office4 | official_name | order | ordination | organisation | organisationposition | organization | organizationposition | other_name | other_names | other_post | parents | partner | party | pen name | pen_name | Period | period | philosophy | political party | political_party | post | post-nominals | posthumous name | posthumous_name | pre-nominals | Predecessor | predecessor | predecessor1 | predecessor2 | predecessor3 | predecessor4 | present post | present_post | previous post | previous_post | profession | pronunciation | rank | Rank | Reason | reason | region | reincarnation of | reincarnation_of | relations | relatives | religion | residence | resting_place | resting_place_coordinates | school | school_tradition | sect | semicha | serviceyears | signature | signature alt | signature_alt | signature_size | spouse | students | subsect | Successor | successor | successor1 | successor2 | successor3 | successor4 | Sufi_order | synagogue | synagogueposition | teacher | teachers | temple | term_end1 | term_end2 | term_end3 | term_end4 | term_start1 | term_start2 | term_start3 | term_start4 | term1 | term2 | term3 | term4 | title | tomb | unit | website | works | yahrtzeit | yeshiva | yeshivaposition }}{{#if:{{{religion|}}}||{{main other|[[Category:Pages using infobox religious biography without religion parameter]]}} }}{{#invoke:Check for conflicting parameters|check | template = [[Template:Infobox religious biography]] | cat = {{main other|Category:Pages using infobox religious biography with conflicting parameters}} | child; embed | honorific prefix; honorific_prefix; honorific-prefix; pre-nominals | honorific suffix; honorific_suffix; honorific-suffix; post-nominals | native_name_lang; native name lang | native-name; native_name; native name | birth_name; birthname | buried; tomb; burial_place | partner; spouse | era; dynasty | political_party; political party; party | main_interests; main interests | ideas; notable_ideas; notable ideas | works; notable_works; notable works | education; alma_mater | other_names; other_name; alias | relations; relatives | honors; honours | signature alt; signature_alt | church; churches | Maddhab; Madh'hab; jurisprudence | school_tradition; creed | dharma_names; dharma_name | guru; teacher | present_post; present post | period; Period | predecessor; Predecessor | successor; Successor | reason; Reason | previous_post; previous post | reincarnation_of; reincarnation of | organizationposition; organisationposition | organization; organisation | rank; Rank }}<noinclude> {{Documentation}} </noinclude> 4psmxjgtrl9ss94m6we9kgdnu4fce7v इस्कूल 0 83797 797113 781654 2026-06-08T20:06:29Z SM7 3953 फोटो जोड़ल गइल, +विकिकड़ी जोड़ल गइल 797113 wikitext text/x-wiki [[चित्र:Perumacheri AUP School.JPG|thumb|केरल के देहाती इलाका में एगो इस्कूल के बिल्डिंग]] '''इस्कूल''' ({{Lang|en|school}}; [[हिंदी भाषा|हिंदी]]: विद्यालय; [[उर्दू]]: मदरसा) एक किसिम के [[शिक्षा]] देवे वाला संस्थान होला जे लड़िकन के पढ़ाई करे खातिर जगह आ माहौल देवे खातिर होला, जहाँ बिद्यार्थी मास्टर साहब लोगन द्वारा कुछ सीख-पढ़ सके। अधिकतर देसन में लड़िका-लड़िकिन के पढ़ाई-लिखाई खातिर औपचारिक सिस्टम होखे ला, कई जगहा त ई अनिवार्य (कंपलसरी) होला। एह पढ़ाई लिखाई के औपचारिक सिस्टम में बिद्यार्थी छोट लेवल से ऊपर के लेवल ले कई इस्कूल सभ से हो के आगे बढ़े ला आ आपन शिक्षा पूरा करे ला। अलग-अलग देसन में कुछ हेरफेर के साथ आ अलग-अलग नाँव से अइसन शिक्षण संस्थान होखे लें। जइसे कि छोट लड़िकन खातिर [[प्राइमरी इस्कूल]], ओकरा बाद [[सेकेंडरी इस्कूल]] आ एही तरीका से आगे [[कॉलेज]] आ [[यूनिवर्सिटी]] ले के पूरा बेवस्था होखे ले जेकरा से गुजर के निचला दर्जा से ले के ऊँच दर्जा तक ले के शिक्षा हासिल कइल जा सके ला। {{clear}} [[श्रेणी:शिक्षा]] {{education-stub}} ovyr06jm3a7it82skwv4suvgvql129j शिक्षाशास्त्र 0 83899 797112 781660 2026-06-08T20:03:00Z SM7 3953 अंग्रेजी विकिपीडिया से अनुबाद क के बिस्तार कइल गइल 797112 wikitext text/x-wiki {{distinguish|शिक्षा बिज्ञान|एजुकेशन}} '''शिक्षाशास्त्र''' ({{Lang|en|Pedagogy}}, {{IPAc-en|ˈ|p|ɛ|d|ə|ɡ|ɒ|dʒ|i|,_|-|ɡ|oʊ|dʒ|i|,_|-|ɡ|ɒ|ɡ|i}}; ''पेड'गोजी'') पढ़ावे (टीचिंग) के कला, बिज्ञान आ पेशा हवे।<ref>{{cite web |title=Definition of PEDAGOGY |url=https://www.merriam-webster.com/dictionary/pedagogy |website=www.merriam-webster.com |access-date=17 फरवरी 2022 |language=en}}</ref> ई ज्ञान-बिज्ञान के शाखा आ अध्ययन के बिसय हवे जेह में पढ़ाई, पढ़ावे के तरीका, सीखल आ सीखे के तरीका; आ इनहन पर बिबिध सामाजिक, मनोबैज्ञानिक तत्वन के परभाव वगैरह के अध्ययन कइल जाला। एक दूसर नाँव '''एजुकेशन''' चाहे '''एडुकेशन''' (education) हवे;<ref>{{cite web |title=Definition of EDUCATION |url=https://www.merriam-webster.com/dictionary/education |website=www.merriam-webster.com |access-date=17 फरवरी 2022 |language=en}}</ref> हालाँकि [[शिक्षा बिज्ञान]] भा एजुकेशन साइंस के आधुनिक समय में पेडागोजी से ब्यापक बिसय मानल जाला। पेडागोजी के आम परिभाषा दिहल जाला — पढ़ावे के बिधी के अध्ययन, जेह में शिक्षा के मकसद आ एह मकसद सभ के हासिल करे के तरीकवो सामिल होखें।<ref>{{cite web |title=pedagogy {{!}} Methods, Theories, & Facts {{!}} Britannica |url=https://www.britannica.com/science/pedagogy |website=www.britannica.com |access-date=17 फरवरी 2022 |language=en |quote='''pedagogy''', the study of teaching methods, including the aims of education and the ways in which such goals may be achieved. |url-status=live}}</ref> पेडागॉजी के अक्सरहा पढ़ावे-लिखावे के कला आ प्रक्रिया (आर्ट आ प्रॉसेस) के रूप में वर्णित कइल जाला। शिक्षक जवन शिक्षण पद्धति अपनावेलन, ऊ उनका के पढ़ावे के तरीका, निर्णय आ शिक्षण रणनीति के आकार देले। एह प्रक्रिया में सीखला के सिद्धांत, विद्यार्थी के जरूरत, समझ, पृष्ठभूमि आ व्यक्तिगत रुचि जइसन बातन के ध्यान में रखल जाला।शिक्षाशास्त्र के मकसद ब्यापक हो सकेला। ई एक ओर उदार शिक्षा (लिबरल एजुकेशन) के माध्यम से मनुष्य के समग्र विकास पर जोर दे सकेला, त दुसरे ओर व्यावसायिक शिक्षा (वोकेशनल एजुकेशन) के तहत खास कौशल सिखावे आ सीखे पर केंद्रित हो सकेला। शिक्षण रणनीति विद्यार्थी के पहिले से मौजूद ज्ञान, अनुभव, परिस्थिति, वातावरण आ शिक्षक-विद्यार्थी द्वारा निर्धारित सीखला के लक्ष्य के आधार पर तय कइल जाला। एह तरह के शिक्षण पद्धति के एगो प्रसिद्ध उदाहरण सुकराती पद्धति (सॉक्रेटिक मेथड) हवे, जवना में सवाल-जवाब के माध्यम से ज्ञान आ समझ विकसित कइल जाला। {{clear}} == संदर्भ == {{Reflist|33em}} [[श्रेणी:शिक्षाशास्त्र| ]] [[श्रेणी:सामाजिक बिज्ञान]] [[श्रेणी:शिक्षा| ]] {{Education-stub}} 1ni6xyn3443qtalpaoph2otuq0rz5i5 797167 797112 2026-06-08T20:25:19Z SM7 3953 797167 wikitext text/x-wiki {{distinguish|शिक्षा बिज्ञान|एजुकेशन}} '''शिक्षाशास्त्र''' ({{Lang|en|Pedagogy}}, {{IPAc-en|ˈ|p|ɛ|d|ə|ɡ|ɒ|dʒ|i|,_|-|ɡ|oʊ|dʒ|i|,_|-|ɡ|ɒ|ɡ|i}}; ''पेड'गोजी'') पढ़ावे (टीचिंग) के कला, बिज्ञान आ पेशा हवे।<ref>{{cite web |title=Definition of PEDAGOGY |url=https://www.merriam-webster.com/dictionary/pedagogy |website=www.merriam-webster.com |access-date=17 फरवरी 2022 |language=en}}</ref> ई ज्ञान-बिज्ञान के शाखा आ अध्ययन के बिसय हवे जेह में पढ़ाई, पढ़ावे के तरीका, सीखल आ सीखे के तरीका; आ इनहन पर बिबिध सामाजिक, मनोबैज्ञानिक तत्वन के परभाव वगैरह के अध्ययन कइल जाला। एक दूसर नाँव '''एजुकेशन''' चाहे '''एडुकेशन''' (education) हवे;<ref>{{cite web |title=Definition of EDUCATION |url=https://www.merriam-webster.com/dictionary/education |website=www.merriam-webster.com |access-date=17 फरवरी 2022 |language=en}}</ref> हालाँकि [[शिक्षा बिज्ञान]] भा एजुकेशन साइंस के आधुनिक समय में पेडागोजी से ब्यापक बिसय मानल जाला। पेडागोजी के आम परिभाषा दिहल जाला — पढ़ावे के बिधी के अध्ययन, जेह में शिक्षा के मकसद आ एह मकसद सभ के हासिल करे के तरीकवो सामिल होखें।<ref>{{cite web |title=pedagogy {{!}} Methods, Theories, & Facts {{!}} Britannica |url=https://www.britannica.com/science/pedagogy |website=www.britannica.com |access-date=17 फरवरी 2022 |language=en |quote='''pedagogy''', the study of teaching methods, including the aims of education and the ways in which such goals may be achieved. |url-status=live}}</ref> पेडागॉजी के अक्सरहा पढ़ावे-लिखावे के कला आ प्रक्रिया (आर्ट आ प्रॉसेस) के रूप में वर्णित कइल जाला। शिक्षक जवन शिक्षण पद्धति अपनावेलन, ऊ उनका के पढ़ावे के तरीका, निर्णय आ शिक्षण रणनीति के आकार देले। एह प्रक्रिया में सीखला के सिद्धांत, विद्यार्थी के जरूरत, समझ, पृष्ठभूमि आ व्यक्तिगत रुचि जइसन बातन के ध्यान में रखल जाला।शिक्षाशास्त्र के मकसद ब्यापक हो सकेला। ई एक ओर उदार शिक्षा (लिबरल एजुकेशन) के माध्यम से मनुष्य के समग्र विकास पर जोर दे सकेला, त दुसरे ओर व्यावसायिक शिक्षा (वोकेशनल एजुकेशन) के तहत खास कौशल सिखावे आ सीखे पर केंद्रित हो सकेला। शिक्षण रणनीति विद्यार्थी के पहिले से मौजूद ज्ञान, अनुभव, परिस्थिति, वातावरण आ शिक्षक-विद्यार्थी द्वारा निर्धारित सीखला के लक्ष्य के आधार पर तय कइल जाला। एह तरह के शिक्षण पद्धति के एगो प्रसिद्ध उदाहरण सुकराती पद्धति (सॉक्रेटिक मेथड) हवे, जवना में सवाल-जवाब के माध्यम से ज्ञान आ समझ विकसित कइल जाला। === शिक्षण के बिधि === {{Excerpt|शिक्षण बिधि|paragraphs=1|only=paragraphs}} {{clear}} == संदर्भ == {{Reflist|33em}} [[श्रेणी:शिक्षाशास्त्र| ]] [[श्रेणी:सामाजिक बिज्ञान]] [[श्रेणी:शिक्षा| ]] {{Education-stub}} rynbsf0na36trultudsq25dym3z5ac3 मुगलसराय जंक्शन रेलवे स्टेशन 0 88222 797098 773815 2026-06-08T14:24:18Z SM7 3953 सफाई कइल गइल 797098 wikitext text/x-wiki {{Infobox station | name=Mughalsarai Junction<br />{{small|Pt. Deen Dayal Upadhyaya Junction}} | native_name_lang= | style=Indian Railways | type=[[Express train|Express]] and [[Passenger train|passenger]] station | image = Deendayalmughalsaraijn.png | image_size = | image_caption = पं. दीन दयाल उपाध्याय जंक्शन रेलवे स्टेशन | address=[[Mughalsarai]] – 232101, [[Uttar Pradesh]] | country=India | coordinates={{Coord|25.2767|N|83.1173|E|type:railwaystation_region:IN|format=dms|display=inline,title}} | elevation={{convert|79.273|m|ft}} | owned=[[Indian Railways]] | operator=[[East Central Railway zone|East Central Railways]] | lines=[[Howrah–Delhi main line]],<br />[[Howrah–Gaya–Delhi line]],<br />[[Howrah–Allahabad–Mumbai line]],<br />[[Gaya–Mughalsarai section]],<br />[[Mughalsarai–Kanpur section]],<br />[[Grand Chord]],<br />[[Patna–Mughalsarai section]],<br />Mughalsarai–Varanasi–Lucknow section | platforms=8 | tracks=23 | connections=Auto stand | ADA= | bicycle=Yes | parking=Yes | structure=Standard on ground | code={{Indian railway code | code = DDU | zone = [[East Central Railway zone]] | division = {{rwd| Mughalsarai}} }} status=Functioning opened={{start date and age|df=yes|1862}} rebuilt= electrified=1961–63 passengers=3 lakh passengers per day pass_system= pass_year= pass_percent= map_type=India Uttar Pradesh map_dot_label=Mughalsarai Junction map_caption=Location in [[Uttar Pradesh]] }} [[Category:Articles using Infobox station with markup inside name]] '''मुगलसराय जंक्शन''', आधिकारिक तौर पर '''पं. दीन दयाल उपाध्याय जंक्शन''', (स्टेशन कोड: '''डीडीयू''', पहिले '''एमजीएस''' ) भारत के [[उत्तर प्रदेश]] राज्य के [[मुगलसराय]] शहर में एगो रेलवे स्टेशन बा। <ref>{{Cite news|url=https://www.indiatoday.in/education-today/gk-current-affairs/story/mughalsarai-station-renamed-1306483-2018-08-06|title=After 156 years, Mughalsarai station renamed as Pandit Deen Dayal Upadhyaya Junction: Know all about it|date=6 August 2018|work=[[India Today]]|access-date=17 June 2021|language=en}}</ref> एह स्टेशन पर एशिया के सभसे बड़हन रेलवे मार्शलिंग यार्ड बा। <ref>{{Cite web|url=https://www.irfca.org/faq/faq-yard.html|title=[IRFCA] Indian Railways FAQ: Freight Sheds and Marshalling Yards|publisher=IRFCA|access-date=7 January 2020}}</ref> मुगलसराय यार्ड में एक महीना में लगभग 450–500 ट्रेन के सुविधा मिलेला। <ref>{{Cite news|url=https://www.livemint.com/Politics/JnG2X7OAKevymvTvGEthOK/Railways-to-invest-Rs3000-crore-to-mechanize-automate-yard.html|title=Railways to invest Rs3,000 crore to mechanize, automate yards|last=Sood|first=Jyotika|date=17 October 2017|work=[[Mint (newspaper)|Mint]]|access-date=1 February 2021|language=en}}</ref> प्रीमियम श्रेणी के पूरब ओर जाए वाली राजधानी ट्रेन आ दुरोंतो ट्रेन समेत सगरी ट्रेन रुक जाले (एहसे पूरा भारतीय रेलवे नेटवर्क में एकरा के अनोखा बनावेला; जवन एकरा के प्रयागराज जंक्शन, भोपाल जंक्शन, आगरा कैंट, ग्वालियर जंक्शन, खड़गपुर, नागपुर आदि जइसन अन्य प्रमुख रेलवे स्टेशनन से अलग करेला) एह स्टेशन पर बा। मुगलसराय में प्रमुख इंस्टालेशन में 147 इंजन वाला इलेक्ट्रिक इंजन शेड, 53 इंजन वाला डीजल इंजन शेड, वैगन आरओएच शेड अउरी 169 बेड के डिवीजनल अस्पताल शामिल बा। == इतिहास == ईस्ट इंडियन रेलवे कंपनी दिल्ली आ हावड़ा के जोड़े के काम उन्नीसवीं सदी के मध्य से शुरू कइलस। कराची के लगे (अब पाकिस्तान में) गद्दर के बाद ई दुसरा सभसे बड़ रेलवे स्टेशन रहल जे 1862 में ब्रिटिश शासन के दौरान बनल। परसिद्ध रूप से पूरबी भारत के प्रवेश द्वार के रूप में जानल जाए वाला ई जंक्शन दिल्ली-कलकत्ता मार्ग के जोड़े के प्रोजेक्ट के हिस्सा के रूप में ब्रिटिश रेलवे कंपनी द्वारा बनावल गइल रहे जेकरा के ईस्ट इंडियन रेलवे के नाँव से जानल जाला। ई स्टेशन ग्रांड ट्रंक रोड मार्ग पर स्थित बा। ई मुगल जमाना के सबसे व्यस्त गलियारा में से एगो रहे जवन पूरबी भारत के उत्तर से जोड़त रहे। 1862 में रेलवे के पटरी मुगलसराय के पार क के [[जमुना|यमुना]] के पच्छिमी किनारे पहुँचल।<ref>{{Cite news|url=https://timesofindia.indiatimes.com/city/varanasi/the-many-names-of-mughalsarai/articleshow/59924547.cms|title=Mughalsarai: The many names of Mughalsarai|last=Dikshit|first=Rajeev|date=5 August 2017|work=[[The Times of India]]|access-date=1 February 2021|language=en}}</ref> दिल्ली के थ्रू लिंक के स्थापना 1866 में भइल <ref>{{Cite web|url=http://www.irfca.org/faq/faq-hist.html|title=IR History: Early History (1832–1869)|publisher=IRFCA|access-date=19 June 2013}}</ref> ग्रांड कॉर्ड के कमीशन 1906 में भइल। <ref>{{Cite web|url=http://www.irfca.org/faq/faq-history3.html|title=IR History: Part III (1900–1947)|publisher=IRFCA|access-date=19 June 2013}}</ref> [[गंगा नदी|गंडक]] के पार [[राजघाट पुल|डफरिन पुल]] 1887 में खुलल रहे जवन मुगलसराय के [[बनारस कैंट रेलवे स्टेशन|वाराणसी]] से जोड़े ला।<ref>{{Cite web|url=http://www.irfca.org/faq/faq-history2.html|title=IR History: Part II (1870–1899)|publisher=IRFCA|access-date=19 June 2013}}</ref> == नाँव बदलाव == ग्रांड ट्रंक रोड पर स्थापित ई स्टेशन एगो रोचक अतीत के सहेजे ला। शेर शाह सूरी द्वारा बनावल गइल ई सड़क अधिकतर कारवां सभ खातिर मुख्य मारग के काम कइलस, मध्यकालीन दौर में आ बहुत बाद में भी, पूर्वी भा दक्खिन भारत से उत्तर भारत के ओर यात्रा कइलस। जेतना व्यस्त रहे, आ अबहियों बा, सड़क के दुनो ओर कई गो सराय रहली स आ एही से एकर नाम — मुगलसराय पड़ल। भारतीय जनसंघ के अध्यक्ष चुनला गइला के मुश्किल से दू महीना बाद 10 फरवरी 1968 के सांझ के दीन दयाल उपाध्याय लखनऊ से पटना खातिर सीयालदह एक्सप्रेस में सवार भइलन। कुछ घंटा बाद मुगलसराय स्टेशन प एगो प्लेटफार्म के छोर से कुछ सौ फीट दूर एगो खंभा के लगे उनुकर लाश मिलल। एकरा बाद जवन भइल उ एगो लंबा अउरी शामिल जांच रहे कि संघ जवना प जोर देले रहे कि उ राजनीतिक मकसद से भइल हत्या ह। सीबीआई के जांच एकरा के दुर्घटना कहलस; दू गो आदमी डकैती के कोशिश में उनुकरा के ट्रेन से बाहर धकेले के कबूल कइले बाकिर सबूत के कमी का चलते बरी कर दिहल गइल; उपाध्याय के शरीर पर कवनो संघर्ष भा चोट के निशान ना रहे। आ संघ में सत्ता के आंतरिक लड़ाई का बारे में षड्यंत्र सिद्धांत आजुओ भरपूर बा। 1992 में [[उत्तर प्रदेश]] राज्य के तत्कालीन सरकार मुगलसराय के नाँव [[दीनदयाल उपाध्याय|दीन दयाल उपाध्याय]] के नाँव पर रखे के कोसिस कइलस हालाँकि, बाबरी महजिद ध्वंस के बाद राज्य में हिंसा के प्रकोप के बाद मुख्यमंत्री कल्याण सिंह के इस्तीफा देवे के पड़ला पर ई योजना ठंडा बस्ता में हो गइल।<ref name="rename">{{Cite news|url=https://www.indiatoday.in/india/story/mughalsarai-station-now-deen-dayal-upadhyay-1305833-2018-08-05|title=Mughalsarai station is now Deen Dayal Upadhyay station|date=5 August 2018|work=[[India Today]]|access-date=21 August 2018}}</ref> 2017 [[भारत सरकार|में भारत सरकार]] [[आदित्यनाथ|योगी आदित्यनाथ]] के नेतृत्व में राज्य सरकार द्वारा भेजल गइल एगो नया प्रस्ताव के मंजूरी दिहलस जवना में स्टेशन के नाम बदल दिहल गइल। <ref>{{Cite news|url=https://indianexpress.com/article/india/here-are-the-railway-stations-which-have-been-renamed-recently-4781759/|title=Mughalsarai railway station renamed after Deen Dayal Upadhyaya: A look at stations that have been renamed recently|date=4 August 2017|work=[[The Indian Express]]|access-date=21 August 2018}}</ref> एह स्टेशन के आधिकारिक रूप से 4 जून 2018 के नाँव बदल के पंडित दीन दयाल उपाध्याय जंक्शन रखल गइल। <ref name="rename" /> == विद्युतीकरण == गया–मुगलसराय जंक्शन सेक्टर के विद्युतीकरण 1961-63 में भइल। मुगलसराय यार्ड में 1963-65 में विद्युतीकरण भइल। <ref>{{Cite web|url=http://irfca.org/docs/electrification-history.html|title=History of Electrification|publisher=IRFCA|access-date=19 June 2013}}</ref> == मार्शलिंग यार्ड == मुगलसराय मार्शलिंग यार्ड एशिया के सबसे बड़ बा। <ref name="marshalling">{{Cite web|url=http://www.irfca.org/faq/faq-yard.html|title=Freight Sheds and Mashalling Yards|publisher=IRFCA|access-date=19 June 2013}}</ref> <ref>{{Cite web|url=http://www.ecr.indianrailways.gov.in/uploads/files/1322119784210-Inf.pdf|title=General Information|publisher=East Central Railway|access-date=19 June 2013}}</ref> <ref>{{Cite web|url=http://www.outlookindia.com/article.aspx?210638|title=Mughalsarai: Tracks to Nowhere|publisher=Outlook India, 8 January 2001|access-date=19 June 2013}}</ref> ई 12.5&nbsp;किमी लंबा बाटे आ डेली लगभग 1,500 वैगन के संभालेला। रेलवे के ओर से टुकड़ा-टुकड़ा लोडिंग बंद होखला के बाद वैगन हैंडलिंग में गिरावट आइल बा। अपना चरम पर ई रोज 5,000 वैगन के संभालत रहे। भारतीय रेलवे पर मौजूद सगरी डिवीजनन में से मुगलसराय डिवीजन में सबसे जादा ट्रेन के संचालन होला – गुड्स आ कोचिंग दुनु के। ई भारत के पूरबी हिस्सा आ उत्तरी हिस्सा के बीच के पुल हवे। ई पिट हेड कोयला आ पावर हाउस, उपयोगकर्ता लोगन खातिर तैयार स्टील उत्पाद, देश के पूर्वी हिस्सा से खाद्य अनाज आ खाद आ उद्योगन के अन्य कच्चा माल के बीच के दूरी कम करे ला। पूर्वी मध्य रेलवे के दक्षता के निर्धारण में डिवीजन के परिचालन दक्षता के अहम भूमिका होला आ एह डिवीजन पर संचालन में कवनो तरह के झटका भा अक्षमता एगो संवेदनशील मामला बा जवन रेलवे के समग्र संचालन के प्रभावित करेला। अपना बहुते महत्व का चलते रेलवे बोर्ड मुगलसराय संभाग के कामकाज पर खास नजर राखेला. <ref name="marshalling" /> <ref>{{Cite web|url=http://indianrailwayemployee.com/node/6016|title=Marshalling Yards|publisher=Indian Railway Employee|access-date=19 June 2013|archive-date=15 January 2013|archive-url=https://web.archive.org/web/20130115212621/http://indianrailwayemployee.com/node/6016|url-status=dead}}</ref> == शेड आ वर्कशाप == मुगल सराय डीजल लोको शेड में WDM-2, WDM-3A आ WDS-5 डीजल लोको के घर बा। डीजल शेड में 50 इलेक्ट्रिक लोको भी बा, जवन कि सभ WAG-7 बा। मुगलसराय में उत्तरी रेलवे के डीजल लोको शेड रहे। एकरा के 2001 में बंद कर दिहल गइल। मुगलसराय इलेक्ट्रिक लोको शेड में 150 से अधिका इलेक्ट्रिक लोको राखल जा सकेलें। एहमें वैप-4 आ 70 से अधिका वैग-7 लोको शामिल बाड़ें। इलेक्ट्रिक शेड में हाल ही में वैग-9 इंजन राखल शुरू हो गइल बा। भारतीय रेलवे के सबसे बड़ वैगन मरम्मत कार्यशाला मुगलसराय में बा। <ref name="sheds">{{Cite web|url=http://www.irfca.org/faq/faq-shed.html|title=Sheds and workshops|publisher=IRFCA|access-date=19 June 2013}}</ref> == यात्री लोग के आवाजाही == पं. दीन दयाल उपाध्याय जंक्शन भारतीय रेलवे के टॉप सौ बुकिंग स्टेशनन में शामिल बा।<ref>{{Cite web|url=http://www.indianrail.gov.in/7days_Avl.html|title=Indian Railways Passenger Reservation Enquiry|website=Availability in trains for Top 100 Booking Stations of Indian Railways|publisher=IRFCA|archive-url=https://web.archive.org/web/20140510115649/http://www.indianrail.gov.in/7days_Avl.html|archive-date=10 May 2014|access-date=19 June 2013}}</ref> == सुविधा सभ == पं. दीन दयाल उपाध्याय जंक्शन रेलवे स्टेशन में 2 गो एसी कमरा, 4 गो गैर-एसी रिटायरिंग कमरा, आ दस बेड वाला गैर-एसी डोरमेटरी बा। एकरा में फूड प्लाजा आ 'जन आहर' (सस्ती खाना) के सुविधा बा। एह स्टेशन पर राष्ट्रीयकृत बैंकन के एटीएम बा।<ref>{{Cite web|url=http://www.ecr.indianrailways.gov.in/uploads/files/1327473148418-Organizational%20Information-JAN-12.pdf|title=Mughalsarai Division, Commercial Department|publisher=Indian Railways|access-date=19 June 2013}}</ref> == गैलरी == <gallery widths="180"> चित्र:Pt. Deen Dayal Upadhyaya Junction board.jpg|Board of the Pt. Deen Dayal Upadhyaya Junction. चित्र:Food Track at Mughalsarai Junction platform 6.jpg|Food Track at Mughalsarai Junction platform 6 चित्र:Howrah-New Delhi Duronto Express on Platform 6 of Mughalsarai Junction.jpg|12273 Howrah-New Delhi Duronto Express on Platform 6 of Mughalsarai Junction चित्र:Upper Class waiting room at Platform 6 of Mughalsarai Junction, India.jpg|Upper Class waiting room at Platform 6 of Mughalsarai Junction चित्र:Platform 7 of Mughalsarai Junction, India.jpg|Platform 7 of Mughalsarai Junction. चित्र:Platform 4 and 5 of Mughalsarai Junction from flyover.jpg|Platform 4 and 5 of Mughalsarai Junction from flyover चित्र:Mughalsarai Junction from flyover 01.jpg|A view of Mughalsarai Junction as seen from flyover. चित्र:Local Train on Platform 5 of Mughalsarai Junction.jpg|Local Train on Platform 5 of Mughalsarai Junction. चित्र:Green colored coach of 12273 Howrah-New Delhi Duronto Express.jpg| </gallery> == इहो देखल जाय == * [[बनारस कैंट रेलवे स्टेशन|वाराणसी जंक्शन रेलवे स्टेशन]] * [[वाराणसी सिटी रेलवे स्टेशन]] * [[काशी रेलवे स्टेशन]] * [[बनारस रेलवे स्टेशन]] * [[गया जंक्शन रेलवे स्टेशन]] * [[धनबाद जंक्शन रेलवे स्टेशन]] == संदर्भ == {{Reflist|33em}} [[श्रेणी:उत्तर प्रदेश में रेलवे स्टेशन]] 12a7uef8s10w2s7qv5jajdj0c9fgnbs 797099 797098 2026-06-08T14:25:15Z SM7 3953 Protected "[[मुगलसराय जंक्शन रेलवे स्टेशन]]" ([संपादन करीं=Allow only administrators] (indefinite) [स्थानांतरण=Allow only administrators] (indefinite)) 797098 wikitext text/x-wiki {{Infobox station | name=Mughalsarai Junction<br />{{small|Pt. Deen Dayal Upadhyaya Junction}} | native_name_lang= | style=Indian Railways | type=[[Express train|Express]] and [[Passenger train|passenger]] station | image = Deendayalmughalsaraijn.png | image_size = | image_caption = पं. दीन दयाल उपाध्याय जंक्शन रेलवे स्टेशन | address=[[Mughalsarai]] – 232101, [[Uttar Pradesh]] | country=India | coordinates={{Coord|25.2767|N|83.1173|E|type:railwaystation_region:IN|format=dms|display=inline,title}} | elevation={{convert|79.273|m|ft}} | owned=[[Indian Railways]] | operator=[[East Central Railway zone|East Central Railways]] | lines=[[Howrah–Delhi main line]],<br />[[Howrah–Gaya–Delhi line]],<br />[[Howrah–Allahabad–Mumbai line]],<br />[[Gaya–Mughalsarai section]],<br />[[Mughalsarai–Kanpur section]],<br />[[Grand Chord]],<br />[[Patna–Mughalsarai section]],<br />Mughalsarai–Varanasi–Lucknow section | platforms=8 | tracks=23 | connections=Auto stand | ADA= | bicycle=Yes | parking=Yes | structure=Standard on ground | code={{Indian railway code | code = DDU | zone = [[East Central Railway zone]] | division = {{rwd| Mughalsarai}} }} status=Functioning opened={{start date and age|df=yes|1862}} rebuilt= electrified=1961–63 passengers=3 lakh passengers per day pass_system= pass_year= pass_percent= map_type=India Uttar Pradesh map_dot_label=Mughalsarai Junction map_caption=Location in [[Uttar Pradesh]] }} [[Category:Articles using Infobox station with markup inside name]] '''मुगलसराय जंक्शन''', आधिकारिक तौर पर '''पं. दीन दयाल उपाध्याय जंक्शन''', (स्टेशन कोड: '''डीडीयू''', पहिले '''एमजीएस''' ) भारत के [[उत्तर प्रदेश]] राज्य के [[मुगलसराय]] शहर में एगो रेलवे स्टेशन बा। <ref>{{Cite news|url=https://www.indiatoday.in/education-today/gk-current-affairs/story/mughalsarai-station-renamed-1306483-2018-08-06|title=After 156 years, Mughalsarai station renamed as Pandit Deen Dayal Upadhyaya Junction: Know all about it|date=6 August 2018|work=[[India Today]]|access-date=17 June 2021|language=en}}</ref> एह स्टेशन पर एशिया के सभसे बड़हन रेलवे मार्शलिंग यार्ड बा। <ref>{{Cite web|url=https://www.irfca.org/faq/faq-yard.html|title=[IRFCA] Indian Railways FAQ: Freight Sheds and Marshalling Yards|publisher=IRFCA|access-date=7 January 2020}}</ref> मुगलसराय यार्ड में एक महीना में लगभग 450–500 ट्रेन के सुविधा मिलेला। <ref>{{Cite news|url=https://www.livemint.com/Politics/JnG2X7OAKevymvTvGEthOK/Railways-to-invest-Rs3000-crore-to-mechanize-automate-yard.html|title=Railways to invest Rs3,000 crore to mechanize, automate yards|last=Sood|first=Jyotika|date=17 October 2017|work=[[Mint (newspaper)|Mint]]|access-date=1 February 2021|language=en}}</ref> प्रीमियम श्रेणी के पूरब ओर जाए वाली राजधानी ट्रेन आ दुरोंतो ट्रेन समेत सगरी ट्रेन रुक जाले (एहसे पूरा भारतीय रेलवे नेटवर्क में एकरा के अनोखा बनावेला; जवन एकरा के प्रयागराज जंक्शन, भोपाल जंक्शन, आगरा कैंट, ग्वालियर जंक्शन, खड़गपुर, नागपुर आदि जइसन अन्य प्रमुख रेलवे स्टेशनन से अलग करेला) एह स्टेशन पर बा। मुगलसराय में प्रमुख इंस्टालेशन में 147 इंजन वाला इलेक्ट्रिक इंजन शेड, 53 इंजन वाला डीजल इंजन शेड, वैगन आरओएच शेड अउरी 169 बेड के डिवीजनल अस्पताल शामिल बा। == इतिहास == ईस्ट इंडियन रेलवे कंपनी दिल्ली आ हावड़ा के जोड़े के काम उन्नीसवीं सदी के मध्य से शुरू कइलस। कराची के लगे (अब पाकिस्तान में) गद्दर के बाद ई दुसरा सभसे बड़ रेलवे स्टेशन रहल जे 1862 में ब्रिटिश शासन के दौरान बनल। परसिद्ध रूप से पूरबी भारत के प्रवेश द्वार के रूप में जानल जाए वाला ई जंक्शन दिल्ली-कलकत्ता मार्ग के जोड़े के प्रोजेक्ट के हिस्सा के रूप में ब्रिटिश रेलवे कंपनी द्वारा बनावल गइल रहे जेकरा के ईस्ट इंडियन रेलवे के नाँव से जानल जाला। ई स्टेशन ग्रांड ट्रंक रोड मार्ग पर स्थित बा। ई मुगल जमाना के सबसे व्यस्त गलियारा में से एगो रहे जवन पूरबी भारत के उत्तर से जोड़त रहे। 1862 में रेलवे के पटरी मुगलसराय के पार क के [[जमुना|यमुना]] के पच्छिमी किनारे पहुँचल।<ref>{{Cite news|url=https://timesofindia.indiatimes.com/city/varanasi/the-many-names-of-mughalsarai/articleshow/59924547.cms|title=Mughalsarai: The many names of Mughalsarai|last=Dikshit|first=Rajeev|date=5 August 2017|work=[[The Times of India]]|access-date=1 February 2021|language=en}}</ref> दिल्ली के थ्रू लिंक के स्थापना 1866 में भइल <ref>{{Cite web|url=http://www.irfca.org/faq/faq-hist.html|title=IR History: Early History (1832–1869)|publisher=IRFCA|access-date=19 June 2013}}</ref> ग्रांड कॉर्ड के कमीशन 1906 में भइल। <ref>{{Cite web|url=http://www.irfca.org/faq/faq-history3.html|title=IR History: Part III (1900–1947)|publisher=IRFCA|access-date=19 June 2013}}</ref> [[गंगा नदी|गंडक]] के पार [[राजघाट पुल|डफरिन पुल]] 1887 में खुलल रहे जवन मुगलसराय के [[बनारस कैंट रेलवे स्टेशन|वाराणसी]] से जोड़े ला।<ref>{{Cite web|url=http://www.irfca.org/faq/faq-history2.html|title=IR History: Part II (1870–1899)|publisher=IRFCA|access-date=19 June 2013}}</ref> == नाँव बदलाव == ग्रांड ट्रंक रोड पर स्थापित ई स्टेशन एगो रोचक अतीत के सहेजे ला। शेर शाह सूरी द्वारा बनावल गइल ई सड़क अधिकतर कारवां सभ खातिर मुख्य मारग के काम कइलस, मध्यकालीन दौर में आ बहुत बाद में भी, पूर्वी भा दक्खिन भारत से उत्तर भारत के ओर यात्रा कइलस। जेतना व्यस्त रहे, आ अबहियों बा, सड़क के दुनो ओर कई गो सराय रहली स आ एही से एकर नाम — मुगलसराय पड़ल। भारतीय जनसंघ के अध्यक्ष चुनला गइला के मुश्किल से दू महीना बाद 10 फरवरी 1968 के सांझ के दीन दयाल उपाध्याय लखनऊ से पटना खातिर सीयालदह एक्सप्रेस में सवार भइलन। कुछ घंटा बाद मुगलसराय स्टेशन प एगो प्लेटफार्म के छोर से कुछ सौ फीट दूर एगो खंभा के लगे उनुकर लाश मिलल। एकरा बाद जवन भइल उ एगो लंबा अउरी शामिल जांच रहे कि संघ जवना प जोर देले रहे कि उ राजनीतिक मकसद से भइल हत्या ह। सीबीआई के जांच एकरा के दुर्घटना कहलस; दू गो आदमी डकैती के कोशिश में उनुकरा के ट्रेन से बाहर धकेले के कबूल कइले बाकिर सबूत के कमी का चलते बरी कर दिहल गइल; उपाध्याय के शरीर पर कवनो संघर्ष भा चोट के निशान ना रहे। आ संघ में सत्ता के आंतरिक लड़ाई का बारे में षड्यंत्र सिद्धांत आजुओ भरपूर बा। 1992 में [[उत्तर प्रदेश]] राज्य के तत्कालीन सरकार मुगलसराय के नाँव [[दीनदयाल उपाध्याय|दीन दयाल उपाध्याय]] के नाँव पर रखे के कोसिस कइलस हालाँकि, बाबरी महजिद ध्वंस के बाद राज्य में हिंसा के प्रकोप के बाद मुख्यमंत्री कल्याण सिंह के इस्तीफा देवे के पड़ला पर ई योजना ठंडा बस्ता में हो गइल।<ref name="rename">{{Cite news|url=https://www.indiatoday.in/india/story/mughalsarai-station-now-deen-dayal-upadhyay-1305833-2018-08-05|title=Mughalsarai station is now Deen Dayal Upadhyay station|date=5 August 2018|work=[[India Today]]|access-date=21 August 2018}}</ref> 2017 [[भारत सरकार|में भारत सरकार]] [[आदित्यनाथ|योगी आदित्यनाथ]] के नेतृत्व में राज्य सरकार द्वारा भेजल गइल एगो नया प्रस्ताव के मंजूरी दिहलस जवना में स्टेशन के नाम बदल दिहल गइल। <ref>{{Cite news|url=https://indianexpress.com/article/india/here-are-the-railway-stations-which-have-been-renamed-recently-4781759/|title=Mughalsarai railway station renamed after Deen Dayal Upadhyaya: A look at stations that have been renamed recently|date=4 August 2017|work=[[The Indian Express]]|access-date=21 August 2018}}</ref> एह स्टेशन के आधिकारिक रूप से 4 जून 2018 के नाँव बदल के पंडित दीन दयाल उपाध्याय जंक्शन रखल गइल। <ref name="rename" /> == विद्युतीकरण == गया–मुगलसराय जंक्शन सेक्टर के विद्युतीकरण 1961-63 में भइल। मुगलसराय यार्ड में 1963-65 में विद्युतीकरण भइल। <ref>{{Cite web|url=http://irfca.org/docs/electrification-history.html|title=History of Electrification|publisher=IRFCA|access-date=19 June 2013}}</ref> == मार्शलिंग यार्ड == मुगलसराय मार्शलिंग यार्ड एशिया के सबसे बड़ बा। <ref name="marshalling">{{Cite web|url=http://www.irfca.org/faq/faq-yard.html|title=Freight Sheds and Mashalling Yards|publisher=IRFCA|access-date=19 June 2013}}</ref> <ref>{{Cite web|url=http://www.ecr.indianrailways.gov.in/uploads/files/1322119784210-Inf.pdf|title=General Information|publisher=East Central Railway|access-date=19 June 2013}}</ref> <ref>{{Cite web|url=http://www.outlookindia.com/article.aspx?210638|title=Mughalsarai: Tracks to Nowhere|publisher=Outlook India, 8 January 2001|access-date=19 June 2013}}</ref> ई 12.5&nbsp;किमी लंबा बाटे आ डेली लगभग 1,500 वैगन के संभालेला। रेलवे के ओर से टुकड़ा-टुकड़ा लोडिंग बंद होखला के बाद वैगन हैंडलिंग में गिरावट आइल बा। अपना चरम पर ई रोज 5,000 वैगन के संभालत रहे। भारतीय रेलवे पर मौजूद सगरी डिवीजनन में से मुगलसराय डिवीजन में सबसे जादा ट्रेन के संचालन होला – गुड्स आ कोचिंग दुनु के। ई भारत के पूरबी हिस्सा आ उत्तरी हिस्सा के बीच के पुल हवे। ई पिट हेड कोयला आ पावर हाउस, उपयोगकर्ता लोगन खातिर तैयार स्टील उत्पाद, देश के पूर्वी हिस्सा से खाद्य अनाज आ खाद आ उद्योगन के अन्य कच्चा माल के बीच के दूरी कम करे ला। पूर्वी मध्य रेलवे के दक्षता के निर्धारण में डिवीजन के परिचालन दक्षता के अहम भूमिका होला आ एह डिवीजन पर संचालन में कवनो तरह के झटका भा अक्षमता एगो संवेदनशील मामला बा जवन रेलवे के समग्र संचालन के प्रभावित करेला। अपना बहुते महत्व का चलते रेलवे बोर्ड मुगलसराय संभाग के कामकाज पर खास नजर राखेला. <ref name="marshalling" /> <ref>{{Cite web|url=http://indianrailwayemployee.com/node/6016|title=Marshalling Yards|publisher=Indian Railway Employee|access-date=19 June 2013|archive-date=15 January 2013|archive-url=https://web.archive.org/web/20130115212621/http://indianrailwayemployee.com/node/6016|url-status=dead}}</ref> == शेड आ वर्कशाप == मुगल सराय डीजल लोको शेड में WDM-2, WDM-3A आ WDS-5 डीजल लोको के घर बा। डीजल शेड में 50 इलेक्ट्रिक लोको भी बा, जवन कि सभ WAG-7 बा। मुगलसराय में उत्तरी रेलवे के डीजल लोको शेड रहे। एकरा के 2001 में बंद कर दिहल गइल। मुगलसराय इलेक्ट्रिक लोको शेड में 150 से अधिका इलेक्ट्रिक लोको राखल जा सकेलें। एहमें वैप-4 आ 70 से अधिका वैग-7 लोको शामिल बाड़ें। इलेक्ट्रिक शेड में हाल ही में वैग-9 इंजन राखल शुरू हो गइल बा। भारतीय रेलवे के सबसे बड़ वैगन मरम्मत कार्यशाला मुगलसराय में बा। <ref name="sheds">{{Cite web|url=http://www.irfca.org/faq/faq-shed.html|title=Sheds and workshops|publisher=IRFCA|access-date=19 June 2013}}</ref> == यात्री लोग के आवाजाही == पं. दीन दयाल उपाध्याय जंक्शन भारतीय रेलवे के टॉप सौ बुकिंग स्टेशनन में शामिल बा।<ref>{{Cite web|url=http://www.indianrail.gov.in/7days_Avl.html|title=Indian Railways Passenger Reservation Enquiry|website=Availability in trains for Top 100 Booking Stations of Indian Railways|publisher=IRFCA|archive-url=https://web.archive.org/web/20140510115649/http://www.indianrail.gov.in/7days_Avl.html|archive-date=10 May 2014|access-date=19 June 2013}}</ref> == सुविधा सभ == पं. दीन दयाल उपाध्याय जंक्शन रेलवे स्टेशन में 2 गो एसी कमरा, 4 गो गैर-एसी रिटायरिंग कमरा, आ दस बेड वाला गैर-एसी डोरमेटरी बा। एकरा में फूड प्लाजा आ 'जन आहर' (सस्ती खाना) के सुविधा बा। एह स्टेशन पर राष्ट्रीयकृत बैंकन के एटीएम बा।<ref>{{Cite web|url=http://www.ecr.indianrailways.gov.in/uploads/files/1327473148418-Organizational%20Information-JAN-12.pdf|title=Mughalsarai Division, Commercial Department|publisher=Indian Railways|access-date=19 June 2013}}</ref> == गैलरी == <gallery widths="180"> चित्र:Pt. Deen Dayal Upadhyaya Junction board.jpg|Board of the Pt. Deen Dayal Upadhyaya Junction. चित्र:Food Track at Mughalsarai Junction platform 6.jpg|Food Track at Mughalsarai Junction platform 6 चित्र:Howrah-New Delhi Duronto Express on Platform 6 of Mughalsarai Junction.jpg|12273 Howrah-New Delhi Duronto Express on Platform 6 of Mughalsarai Junction चित्र:Upper Class waiting room at Platform 6 of Mughalsarai Junction, India.jpg|Upper Class waiting room at Platform 6 of Mughalsarai Junction चित्र:Platform 7 of Mughalsarai Junction, India.jpg|Platform 7 of Mughalsarai Junction. चित्र:Platform 4 and 5 of Mughalsarai Junction from flyover.jpg|Platform 4 and 5 of Mughalsarai Junction from flyover चित्र:Mughalsarai Junction from flyover 01.jpg|A view of Mughalsarai Junction as seen from flyover. चित्र:Local Train on Platform 5 of Mughalsarai Junction.jpg|Local Train on Platform 5 of Mughalsarai Junction. चित्र:Green colored coach of 12273 Howrah-New Delhi Duronto Express.jpg| </gallery> == इहो देखल जाय == * [[बनारस कैंट रेलवे स्टेशन|वाराणसी जंक्शन रेलवे स्टेशन]] * [[वाराणसी सिटी रेलवे स्टेशन]] * [[काशी रेलवे स्टेशन]] * [[बनारस रेलवे स्टेशन]] * [[गया जंक्शन रेलवे स्टेशन]] * [[धनबाद जंक्शन रेलवे स्टेशन]] == संदर्भ == {{Reflist|33em}} [[श्रेणी:उत्तर प्रदेश में रेलवे स्टेशन]] 12a7uef8s10w2s7qv5jajdj0c9fgnbs कुसुमी जंगल 0 88791 797103 774609 2026-06-08T14:46:49Z SM7 3953 बिस्तार कइल गइल 797103 wikitext text/x-wiki {{Infobox forest | name = कुसुमी जंगल | native_name = | photo = | photo_caption = | photo_width = | map = India Uttar Pradesh | map_caption = उत्तर प्रदेश में कुसुमी जंगल के स्थिति | map_width = | coordinates = {{coord|26.749748|83.468645}} | county = | region = | country = [[भारत]] | elevation = | area = | max_area = | date_max_area = | status = नेशनल फारेस्ट | established = | visitation = | visitation_year = | events = | authority = | website = | ecosystem = | classification_WWF = | classification_EPA = | classification_CEC = | disturbance = | forest_cover = | species = साल (शेखुआ) | indicator_plants = | lesser_flora = | fauna = }} '''कुसुमी जंगल''' (दूसर नाम: कुसमी, कुसुम्ही, कुष्मी) [[उत्तर प्रदेश]] में [[गोरखपुर]] के पास एक ठो [[जंगल]] बाटे। ई [[राष्ट्रीय बन]] हवे जहाँ साल के पेड़ बाने। ई घना जंगल हवे जेकर मैनेजमेंट आ संरक्षण सरकारी रूप से बन बिभाग के देखरेख में होखे ला। हाल में [[उत्तर प्रदेश सरकार]] एकरा के [[इको-टूरिज्म]] के अस्थान बनावे के घोषणा कइले बाटे। ए जंगल में एगो पार्क बा जे के कुसुमी विनोद वन के नाम से जानल जाएला। एही जंगल में एगो मंदिर बाटे जेकरा के बुढ़िया माई मंदिर के नाँव से जानल जाला।<ref name="News18" /> ई सब जगह गोरखपुर के लोगन खातिर पिकनिक स्पॉट के रूप में बाड़ें आ [[न्यू इयर]] पर बहुत सारा लोग इहँवा जाएला। == कुसुमी विनोद वन == कुसुमी जंगल में एगो पार्क बाटे। सरकारी वेबसाइट के अनुसार ई शहर के रेलवे स्टेशन से 9 किलोमीटर के दूर पर बा आ इहँवा एक गो चिड़ियाघर बाटे जहाँ कुछ जानवर रखल बाने।<ref>{{cite web |title=Places of Interest {{!}} District Gorakhpur {{!}} India |url=https://gorakhpur.nic.in/places-of-interest/ |website=gorakhpur.nic.in |publisher=[[उत्तर प्रदेश सरकार]] |access-date=14 फरवरी 2023}}</ref> कुसुमी जंगल के सरकार इको-टूरिज्म पार्क बनावे के योजना बाना रहल बाटी।<ref>{{cite news |title=गोरखपुर के कुसम्ही जंगल में बनेगा इको टूरिज्म सेंटर, पर्यटकों को करेगा आकर्षित |url=https://www.livehindustan.com/uttar-pradesh/story-eco-tourism-center-will-be-built-in-kusumhi-forest-of-gorakhpur-cm-yogi-appreciated-the-proposal-7046736.html |access-date=14 फरवरी 2023 |work=हिंदुस्तान |date=7 सितंबर 2022 |language=hi}}</ref> == बुढ़िया माई मंदिर == जंगल के बीचा में एगो मंदिर बाटे। एहिजा सालों भर भीड़ रहे ला आ लोग बुढ़िया माई भा बुढ़िया माता के दर्शन करे आवे ला। मान्यता हवे कि इहाँ दर्शन करे से मनौती पूरा होखे ला।<ref name="News18" /> हालाँकि, मंदिर आ बुढ़िया माई के साथे भूतिया कथा किंबदंती जुड़ल बाटे। कहल जाला कि जंगल के बीच नाला से हो के एगो बरात गुजरत रहल। गाँव के एगो बुढ़िया बरात से नाच देखावे के कहलस बाकी बरात के लोग बुढ़िया के मजाक उड़ावल। आगे जा के पूरा बरात नाला के पुल से नाला में गिर गइल, खाली भर एक ठो जोकर जिंदा बचल जे बुढ़िया के आपन नाच देखवले रहल। एही के बाद बुढ़िया माता के मंदिर बनवावल गइल।<ref name="News18">{{cite web |title=यूपी के इस शहर में विराजमान है बुढ़िया माता का मंदिर,जानें मान्यता |url=https://hindi.news18.com/news/uttar-pradesh/gorakhpur-temple-of-budhia-mata-situated-in-the-middle-of-this-forest-know-its-importance-7429907.html |website=News18 हिंदी |access-date=8 जून 2026 |language=hi-IN}}</ref> == ईहो देखल जाव == * [[रामगढ़ ताल]] * [[गोरखनाथ मंदिर]] == संदर्भ == {{Reflist|33em}} [[श्रेणी:गोरखपुर]] [[श्रेणी:गोरखपुर के पर्यटक अस्थान]] [[श्रेणी:गोरखपुर जिला]] {{UP-geo-stub}} 9r97al6h08z1exwilmtqre9rug03953 हनुमान चालीसा 0 89219 797603 796053 2026-06-09T08:56:12Z SM7 3953 [[विकिपीडिया:हॉट-कैट|हॉट-कैट]] द्वारा [[श्रेणी:भक्ति आंदोलन]] हटावल गइल 797603 wikitext text/x-wiki {{Infobox religious text |name=हनुमान चालीसा |image=Hanuman showing Rama in His heart.jpg |author=[[तुलसीदास]] |religion=[[हिंदू धर्म]] |language=[[अवधी भाषा]]<ref>Nityanand Misra 2015, p. xviii.</ref> |Genre=[[भक्ति]] साहित्य |verses=40 }} '''हनुमान चालीसा''' चाहे '''हनुमान चलीसा''' [[हिंदू धर्म|हिंदू]] देवता [[हनुमान]] के गुणगान में भक्ति गीत (''[[स्तोत्र]]'') ह।<ref name="mahaviriintro">Rambhadradas 1984, [http://jagadgururambhadracharya.org/works/hcm/amukha.php pp. 1-8.] {{Webarchive|url=https://web.archive.org/web/20140203052448/http://jagadgururambhadracharya.org/works/hcm/amukha.php|date=3 February 2014}}</ref><ref>{{Cite web|url=http://www.thehindubusinessline.com/2003/02/26/stories/2003022601521700.htm|title=Hanuman Chalisa in digital version|date=26 February 2003|publisher=The Hindu Business Line|access-date=2011-06-25}}</ref><ref>{{Cite web|url=https://hindi.news18.com/news/dharm/who-wrote-hanuman-chalisa-the-story-behind-it-2999632.html|title=किसने लिखी थी हनुमान चालीसा, जिसके बारे में कही जाती हैं कई बातें|date=9 April 2020|website=News18 India|access-date=2020-09-15|archive-date=2020-05-04|archive-url=https://web.archive.org/web/20200504125310/https://hindi.news18.com/news/dharm/who-wrote-hanuman-chalisa-the-story-behind-it-2999632.html|url-status=dead}}</ref> [[तुलसीदास]] एकर रचना [[अवधी भाषा]] में कइलें,<ref name="mahaviriintro" /> आ ''[[रामचरितमानस]]'' के अलावा ई इनके सभसे परसिद्ध रचना ह।<ref>{{Cite news|url=http://www.hindu.com/br/2006/01/03/stories/2006010300511400.htm|title=Book Review / Language Books : Epic of Tulasidas|date=3 January 2006|work=The Hindu|access-date=2011-06-25|archive-date=2010-03-04|archive-url=https://web.archive.org/web/20100304141547/http://www.hindu.com/br/2006/01/03/stories/2006010300511400.htm|url-status=dead}}</ref><ref>{{Cite news|url=http://www.hindu.com/thehindu/fr/2002/11/29/stories/2002112900990400.htm|title=Lineage shows|date=29 November 2002|work=[[The Hindu]]|access-date=2011-06-25|archive-url=https://web.archive.org/web/20040103112927/http://www.hindu.com/thehindu/fr/2002/11/29/stories/2002112900990400.htm|archive-date=3 January 2004}}</ref> हिंदू धार्मिक कथा सभ में हनुमान जी [[राम]] के भक्त हवें आ ''[[रामायण]]'' के केंद्रीय पात्रन में से एक हवें। [[शैव मत|शैव]] परंपरा के अनुसार हनुमानो एगो देवता हवन जे [[शिव]] के अवतार हवें। लोककथा सभ में हनुमान जी के बल-बुद्धि के काफी तारीफ मिले ला।<ref name="peeb100">Peebles 1986, p. 100</ref> हनुमान के गुण — उनकर ताकत, साहस, बुद्धि, [[ब्रह्मचर्य]], राम के प्रति उनकर भक्ति; आ जवना कई गो नाँव सभ से उनकरा के जानल जाला — के बिस्तार से ''हनुमान चालीसा'' में बर्णन कइल गइल बा।<ref name="peeb100" /> ''हनुमान चालीसा'' के पाठ भा जप एगो आम हिंदू धार्मिक रिवाज ह।<ref>Peebles 1986, p. 99</ref> ''हनुमान चालीसा'' हनुमान के गुणगान में सबसे लोकप्रिय गीत ह, आ रोज लाखों हिंदू लोग एकर पाठ करे ला।<ref name="nm2015foreword">[[Karan Singh]], in Nityanand Misra 2015, p. xvi.</ref> == बिबरन == ''हनुमान चालीसा'' के अरथ होला — हनुमान पर चालीस ठे चौपाई। "चालीसा" भा "चलीसा" शब्द "चालीस" से बनल बा, जेकर मतलब होला चालीस के संख्या, काहें से कि ''हनुमान चालीसा में'' 40 गो [[चौपाई]] छंद बाड़ें (शुरुआत आ अंत में के दोहा सभ के छोड़ के)।<ref name="mahaviriintro" /> एकर रचना के श्रेय [[तुलसीदास]] के दिहल जाला, जे 16वीं सदी ईसवी में एगो कवि-संत रहलें। स्तोत्र के अंतिम श्लोक में उ आपन नाम के जिकिर कइले बाड़ें। ''हनुमान चालीसा'' के 39वीं चौपाई में कहल गइल बा कि जे [[हनुमान]] जी के पूरा भक्ति से एकर जप करी, ओकरा प हनुमान के कृपा होई। दुनिया भर के हिंदू लोग में ई बहुत लोकप्रिय मान्यता बा कि एह चालीसा के जाप से गंभीर समस्या में हनुमान के दिव्य हस्तक्षेप के आह्वान होला। === लेखक === [[चित्र:Goswami_Tulsidas_Awadhi_Hindi_Poet.jpg|thumb| [[तुलसीदास]] के सबसे आम तस्वीर]] [[चित्र:Tulsi_Das_Home_from_the_Ganga_River_near_Hanuman_Ghat,_Varanasi.jpg|thumb| [[गंगा नदी]] के तीरे तुलसीदास के घर तुलसी घाट, [[बनारस]] जहाँ हनुमान चालीसा लिखल गइल रहे, एह स्थल पर एगो छोट मंदिरो बाटे।]] [[तुलसीदास]]<ref name="frommer" /> (1497/1532-1623) एगो [[हिंदू धर्म|हिंदू]] कवि-संत, सुधारक आ दार्शनिक रहलें जे [[राम]] के प्रति भक्ति खातिर परसिद्ध रहलें। कई गो लोकप्रिय रचना सभ के रचयिता तुलसीदास के सभसे ढेर जानल जाला महाकाव्य ''[[रामचरितमानस]]'' के लेखक के रूप में, जे लोकभाषा में अवधी भाषा में ''[[रामायण]]'' के दोबारा बर्णन हवे। तुलसीदास के उनुका जियते में उनुका के [[संस्कृत]] में मूल रामायण के रचनाकार [[वाल्मीकि]] के पुनर्जन्म के रूप में प्रशंसित कइल गइल रहे।<ref>Lutgendorf 2007, p. 293.</ref> तुलसीदास अपना निधन तक [[बनारस|काशी]] (अब बनारस) में रहत रहलें।<ref>Prasad 2008, p. 857, quoting Mata Prasad Gupta: Although he paid occasional visits to several places of pilgrimage associated with Rama, his permanent residence was in Kashi.</ref> बनारस के तुलसी घाट के नाम उनके नाम पर रखल गइल बा।<ref name="frommer">de Bruyn 2010, p. 471</ref> ऊ बनारस में हनुमान के समर्पित [[संकट मोचन|संकट मोचन हनुमान मंदिर]] के स्थापना कइलें, मानल जाला कि ई ओह जगह पर खड़ा हवे जहाँ इनके हनुमान के साक्षात दर्शन भइल रहल।<ref>Callewaert 2000, p. 90</ref> तुलसीदास [[रामलीला]] नाटकन के शुरुआत कइलन जवन रामायण के लोक-रंगमय रूपांतरण ह।<ref name="handoo-ramlila">Handoo 1964, p. 128: … this book … is also a drama, because Goswami Tulasidasa started his ''Ram Lila'' on the basis of this book, which even now is performed in the same manner everywhere.</ref> [[हिंदी साहित्य|हिंदी]], [[भारतीय साहित्य|भारतीय]], आ विश्व साहित्य के सबसे बड़ कवि में से एक के रूप में उनुकर प्रशंसा मिलल बा।<ref>Prasad 2008, p. xii: He is not only the supreme poet, but the unofficial poet-laureate of India.</ref><ref>Prasad 2008, p. xix: Of Tulsidas's place among the major Indian poets there can be no question: he is as sublime as Valmiki and as elegant as Kalidasa in his handling of the theme.</ref><ref name="jonesryan-tulsi">Jones 2007, p. 456</ref><ref name="nirala-tulsi">Sahni 2000, pp. 78-80</ref> भारत में कला, संस्कृति आ समाज पर तुलसीदास आ इनके रचना सभ के परभाव बहुत ब्यापक बा आ आजु ले लोकभाषा, रामलीला नाटक, हिंदुस्तानी शास्त्रीय संगीत, लोकप्रिय संगीत, आ टेलीविजन धारावाहिक सभ में देखल जाला।<ref name="handoo-ramlila" /><ref>Lutgendorf 1991, p. 11: … — scores of lines from the ''Rāmcaritmānas'' have entered folk speech as proverbs — …</ref><ref>Mitra 2002, p. 216</ref><ref>Subramanian 2008, p. inside cover</ref> === भाषा === ''हनुमान चालीसा'' के 40 गो चौपाई छंद के पहिले शुरू में 2 गो दोहा आ अंत में एगो दोहा बाड़ें।<ref>Mehta 2007, p. xxv</ref> चालीसा में क्रम से ज्ञान, बिना कवनो इच्छा के राम आ मनुष्य के प्रति भक्ति।<ref>Mehta 2007, p. xxvii</ref> जइसे भक्ति साहित्य के मामला में तुलसीदास जी कविता के शुरुआत अपना गुरु (गुरु) के गुणगान करत दू गो दोहा से कइले बाड़न।<ref>Mehta 2007, p. xxxi</ref> चालीसा के भाषा [[अवधी भाषा]] हवे।<ref>Mehta 2007, p. xxxvix</ref> === देवता === [[हिंदू]] देवता जेकर ई प्रार्थना हवे ऊ [[हनुमान]] हवें, जे [[राम]] ([[विष्णु]] के सातवाँ अवतार) आ ''रामायण'' के एगो केंद्रीय पात्र, के कट्टर भक्त हवें। वानर लोग में सेनापति हनुमान राक्षस राजा [[रावण]] के खिलाफ युद्ध में राम के योद्धा रहले। हनुमान के कारनामा के कई किसिम के धार्मिक आ सांस्कृतिक परंपरा सभ में<ref>Orlando O. Espín, James B. Nickoloff ''An introductory dictionary of theology and religious studies''. 2007, page 537</ref> खासतौर पर हिंदू धर्म में, एह हद ले मनावल जाला कि ऊ अक्सर कुछ भक्ति परंपरा सभ के अनुसार पूजा के बिसय होलें,<ref>Rosen, Steven. ''Essential Hinduism''. 2006, page 67-8</ref> आ कई गो मंदिर सभ में ई प्रधान देवता हवें जे जानल जालें हनुमान मंदिर के रूप में। ऊ सात गो चिरंजीवी (अमर) में से एक हवें। हनुमान जी अर्जुन के रथ पर [[महाभारत]] में भी उनकर ध्वज (झंडा) के रूप में आवेला। == पाठ == एह रचना में तेतालीस गो छंद बाड़ें — दू गो परिचयात्मक दोहा, चालीस गो चौपाई आ अंत में एगो दोहा।[2] पहिला परिचयात्मक दोहा के शुरुआत श्री शब्द से होला जवन शिव के कहल जाला, जेकरा के हनुमान के गुरु मानल जाला।[24] हनुमान के शुभ रूप, ज्ञान, गुण, शक्ति आ बहादुरी के वर्णन पहिला दस चौपाई में कइल गइल बा।[25][26][27] चौपाई एगारह से बीस में हनुमान के राम के सेवा में कइल गइल काम के वर्णन बा आ एगारहवाँ से पन्द्रहवाँ चौपाई में लक्ष्मण के चेतना में वापस ले आवे में हनुमान के भूमिका के वर्णन कइल गइल बा।[25] एकइसवीं चौपाई से तुलसीदास हनुमान के कृपा के जरूरत के वर्णन कइले बाड़न।[28] अंत में तुलसीदास हनुमान जी के सूक्ष्म भक्ति से अभिवादन करे लें[29] आ इनके दिल में आ भक्त लोग के दिल में निवास करे के निहोरा करे लें।[30] समापन दोहा में फिर से हनुमान से राम, लक्ष्मण आ सीता के साथे दिल में निवास करे के निहोरा कइल गइल बा।[31] == दोहा == : ''श्रीगुरु चरण सरोज रज निज मनु मुकुर सुधारि । : ''वर्नौ रघुवर विमल जशु जो दायक फल चारि ।।'' : ''बुद्धिहीन तनु जानिके, सुमिरौ पवन कुमार । : ''बल बुद्धि विद्या देहु मोहि, हरहु कलेश विकार ।।'' == चौपाई == : जय हनुमान ज्ञान गुन सागर । : जय कपीस तिहुँ लोक उजागर ॥1॥ : राम दूत अतुलित बल धामा । : अंजनि पुत्र पवनसुत नामा ॥2॥ : महाबीर विक्रम बजरंगी । : कुमति निवार सुमति के संगी ॥3॥ : कंचन बरन बिराज सुबेसा । : कानन कुंडल कुंचित केसा ॥4॥ : हाथ वज्र औ ध्वजा बिराजे । : काँधे मूँज जनेऊ साजे ॥5॥ : संकर सुवन केसरी नंदन । : तेज प्रताप महा जग वंदन ॥6॥ : विद्यावान गु‌‍‍णी अति चातुर । : राम काज करिबे को आतुर ॥7॥ : प्रभु चरित्र सुनिबे को रसिया । : राम लखन सीता मनबसिया ॥8॥ : सूक्ष्म रूप धरि सियहिं दिखावा । : बिकट रूप धरि लंक जरावा ॥9॥ : भीम रूप धरि असुर सँहारे । : रामचंद्र के काज सवाँरे ॥10॥ : लाय सँजीवन लखन जियाए । : श्री रघुबीर हरषि उर लाए ॥11॥ : रघुपति कीन्हीं बहुत बड़ाई । : तुम मम प्रिय भरतहि सम भाई ॥12॥ : सहस बदन तुम्हरो यस गावै । : अस कहि श्रीपति कंठ लगावै ॥13॥ : सनकादिक ब्रह्मादि मुनीसा । : नारद सारद सहित अहीसा ॥14॥ : यम कुबेर दिगपाल जहाँ ते । : कवि कोविद कहि सके कहाँ ते ॥15॥ : तुम उपकार सुग्रीवहिं कीन्हा । : राम मिलाय राज पद दीन्हा ॥16॥ : तुम्हरो मंत्र बिभीषण माना । : लंकेश्वर भये सब जग जाना ॥17॥ : जुग सहस्र योजन पर भानू । : लील्यो ताहि मधुर फल जानू ॥18॥ : प्रभु मुद्रिका मेलि मुख माहीं । : जलधि लाँघि गए अचरज नाहीं ॥19॥ : दुर्गम काज जगत के जेते । : सुगम अनुग्रह तुम्हरे तेते ॥20॥ : राम दुआरे तुम रखवारे । : होत न आज्ञा बिनु पैसारे ॥21॥ : सब सुख लहैं तुम्हारी सरना । : तुम रक्षक काहू को डरना ॥22॥ : आपन तेज सम्हारो आपै । : तीनहुँ लोक हाँक ते काँपै ॥23॥ : भूत पिशाच निकट नहि आवै । : महाबीर जब नाम सुनावै ॥24॥ : नासै रोग हरे सब पीरा । : जपत निरंतर हनुमत बीरा ॥25॥ : संकट तें हनुमान छुडावैं । : मन क्रम वचन ध्यान जो लावै ॥26॥ : सब पर राम तपस्वी राजा । : तिनके काज सकल तुम साजा ॥27॥ : और मनोरथ जो कोई लावै । : सोइ अमित जीवन फल पावै ॥28॥ : चारों जुग परताप तुम्हारा । : है परसिद्ध जगत उजियारा ॥29॥ : साधु संत के तुम रखवारे । : असुर निकंदन राम दुलारे ॥30॥ : अष्ट सिद्धि नौ निधि के दाता । : अस बर दीन जानकी माता ॥31॥ : राम रसायन तुम्हरे पासा । : सदा रहो रघुपति के दासा ॥32॥ : तुम्हरे भजन राम को पावै । : जनम जनम के दुख बिसरावै ॥33॥ : अंतकाल रघुवरपुर जाई । : जहाँ जन्म हरिभक्त कहाई ॥34॥ : और देवता चित्त ना धरई । : हनुमत सेई सर्व सुख करई ॥35॥ : संकट कटै मिटै सब पीरा । : जो सुमिरै हनुमत बलबीरा ॥36॥ : जै जै जै हनुमान गोसाईं । : कृपा करहु गुरु देव की नाईं ॥37॥ : यह सत बार पाठ कर जोई । : छूटहि बंदि महा सुख होई ॥38॥ : जो यह पढ़े हनुमान चालीसा । : होय सिद्धि साखी गौरीसा ॥39॥ : तुलसीदास सदा हरि चेरा । : कीजै नाथ हृदय मँह डेरा ॥40॥ ।। दोहा ।। : ''पवन तनय संकट हरन, मंगल मूरति रूप ।'' : ''राम लखन सीता सहित, हृदय बसहु सुर भूप ॥'' === टीका सभ === 1980 के दशक से पहिले ''हनुमान चालीसा'' पर कवनो टीका ना बनावल गइल रहे, जवना के रामभद्राचार्य एह रचना के तुलसीदास के संग्रहित रचना के मुद्रित संस्करण में शामिल ना होखे के कारण बतावेलें।<ref name="mahaviriintro"/> ''हनुमान चालीसा'' पर पहिला संक्षिप्त टीका इंदुभूषण रामायनी के रचना हवे।<ref name="mahaviriintro" /> रामभद्राचार्य के हिंदी में ''महावीरी'' टीका, जेकर रचना 1983 में भइल,<ref name="mahaviriintro" /> रामचंद्र प्रसाद द्वारा ''हनुमान चालीसा'' पर सभसे नीक टीका कहल गइल। == समीक्षा == स्वामी करपात्री ''हनुमान चालीसा के'' वैदिक मंत्रन के तरह एगो परम ''प्रमाण'', सर्वशक्तिमान आ सभ इच्छा के पूरा करे में सक्षम मानत रहले।<ref name="mahaviriintro"/> रामभद्राचार्य एकरा के शुभता से भरल आ "स्तोत्रन के बीच गहना" कहलें, आ कहलें कि ऊ कई गो अइसन उदाहरण के साक्षी आ सुनले बाड़ें जहाँ आस्था के साथ चालीसा पाठ करे वाला लोग के इच्छा पूरा भइल।<ref name="mahaviriintro" /> == पॉपुलर संस्कृति में == ''हनुमान चालीसा'' के पाठ लाखों हिंदू लोग रोज करे ला<ref name="nm2015foreword">[[Karan Singh]], in Nityanand Misra 2015, p. xvi.</ref> आ भारत के अधिकतर साधक हिंदू लोग के एकर पाठ जबानी इयाद बा।<ref name="nm2015preface">Nityanand Misra 2015, pp. xvii-xxi.</ref> ई रचना बिबिध शैक्षिक, सामाजिक, भाषाई, संगीत, आ भौगोलिक समूह सभ के लोग के बीच लोकप्रिय होखे बा।<ref name="nm2015preface" /> === शास्त्रीय आ लोक संगीत === ''हनुमान चालीसा'' हिंदू धार्मिक किताबन में से एगो ह आ एकरा के कई गो लोकप्रिय भजन, शास्त्रीय आ लोक गायक लोग गवले बा।<ref name="nm2015preface">Nityanand Misra 2015, pp. xvii-xxi.</ref> हरि ओम शरण के ''हनुमान चालीसा'' के प्रस्तुति, मूल रूप से 1974 में भारत के ग्रामोफोन कंपनी द्वारा रिलीज कइल गइल आ 1995 में सुपर कैसेट इंडस्ट्रीज द्वारा दोबारा रिलीज कइल गइल,<ref name="nm2015notation" /> सभसे लोकप्रिय सभ में से एक बा, आ नियमित रूप से पूरा उत्तरी भारत के मंदिर आ घर सभ में बजावल जाला।<ref name="nm2015preface" /> ई प्रस्तुति मिश्र खमाज में पारंपरिक धुन पर आधारित बा, [[ठाट|जवन]] खमाज थाट के एगो [[राग]] ह,<ref name="nm2015notation">Nityanand Misra 2015, pp. 199-212.</ref> जवना के आधार स्वर हारमोनियम के दूसरा करिया कुंजी (''काली दो'') पर लिहल गइल बा।<ref name="nm2015notation" /> एही पारंपरिक धुन पर आधारित रिकार्डिंग सुपर कैसेट इंडस्ट्रीज के ओर से 1992 में रिलीज भइल, जवना में हरिहरन गायक आ गुलशन कुमार कलाकार के रूप में रहल लोग।<ref name="nm2015notation" /> अउरी उल्लेखनीय प्रस्तुति में भजन गायक अनुप जलोटा आ रविंद्र जैन, हिंदुस्तानी शास्त्रीय गायक पंडित जसराज आ राजन आ साजन मिश्रा, आ कर्नाटक गायिका एमएस सुब्बुलक्ष्मी के प्रस्तुति शामिल बा।<ref name="nm2015notation">Nityanand Misra 2015, pp. 199-212.</ref> उन्नी कृष्णन, नित्यश्री महादेवन, पंडित भीमसेन जोशी, गणपति सच्चिदानन्द स्वामीजी आ मोररी बापू के प्रस्तुति भी लोकप्रिय बा। पाश्चात्य गायकन में कृष्ण दास हनुमान चालीसा के धीमा आ तेज दुनु प्रारूप में प्रस्तुत कइले बाड़न.<ref>{{Cite web|url=https://krishnadas.com/podcasts/call-response/spiritual-experiences-auschwitz-and-bernie-glassman/|title=Ep. 27 &#124; Spiritual Experiences, Auschwitz and Bernie Glassman|date=June 15, 2020}}</ref> === पापुलर फिलिम === हिंदी सिनेमा ''1920'' (निर्देशक विक्रम भट्ट) में ''हनुमान चालीसा के'' अक्सर अलग-अलग सीन में इस्तेमाल भइल। एगो सीन में नायक अर्जुन सिंह राठोड (रजनीश दुग्गल के भूमिका में), ''हनुमान चालीसा'' के पूरा पाठ करत देखावल गइल बा। एकर प्रयोग ''बजरंगी भाईजान में एगो महत्वपूर्ण सीक्वेंस में भइल बा,'' जब नायक बाल तस्करन से जवाबी लड़ाई लड़त बा आ ओह लोग से एगो छोट लड़िकी के बचा लेला।<ref>{{Cite web|url=https://timesofindia.indiatimes.com/bajrangi-bhaijaan-plot-summary/articleshow/48108672.cms|title=Bajrangi Bhaijaan Plot Summary – Times of India|website=The Times of India|language=en|access-date=2021-02-01}}</ref> चारुवी अग्रवाल के निर्देशन आ चारुवी डिजाइन लैब्स के डिजाइन कइल ''श्री हनुमान चालीसा'' नाम के एगो एनीमेशन फिलिम हनुमान पर बनल फिलिम ह।<ref>{{Cite web|url=http://animationgalaxy.in/Newsindetail.aspx?Dept=Creatives&PID=96|title=Charuvi Design Labs release The Second official teaser for "Shri Hanuman Chalisa"|archive-url=https://web.archive.org/web/20160423025521/http://animationgalaxy.in/Newsindetail.aspx?Dept=Creatives&PID=96|archive-date=23 April 2016|access-date=2016-04-06}}</ref><ref>{{Cite web|url=http://animationgalaxy.in/Newsindetail.aspx?Dept=Creatives&PID=91|title=Charuvi Design Labs release The first official teaser for "Shri Hanuman Chalisa"|archive-url=https://web.archive.org/web/20160423033637/http://animationgalaxy.in/Newsindetail.aspx?Dept=Creatives&PID=91|archive-date=23 April 2016|access-date=2016-04-06}}</ref> === पापुलर संगीत === ''हनुमान चालीसा'' गावल लोकप्रिय गायकन में कर्नाटक गायक एमएस सुब्बुलक्ष्मी के साथे [[लता मंगेशकर]], महेंद्र कपूर, एसपी बालासुब्रह्मण्यम, शंकर महादेवन, अनुराधा पौडवाल, कैलाश खेर, सुखविंदर सिंह, आ [[उदित नारायण]] शामिल बाड़े।<ref name="nm2015preface">Nityanand Misra 2015, pp. xvii-xxi.</ref> ''हनुमान चालीसा'' [[अमिताभ बच्चन]] बीस गो अउरी गायकन का साथे कोरस में गवले रहले।<ref name="nm2015preface">Nityanand Misra 2015, pp. xvii-xxi.</ref> ई रिकार्डिंग ''श्री हनुमान चालीसा'' एल्बम के हिस्सा के रूप में 2011 में रिलीज भइल आ नवंबर 2011 के दौरान रिलीजिंग म्यूजिक लेबल द्वारा एकरा के अभूतपूर्व प्रतिक्रिया मिलल<ref>{{Cite news|url=http://articles.timesofindia.indiatimes.com/2011-11-06/news-and-interviews/30364066_1_tv-ad-campaign-music-label-album|title=All in praise of the Almighty|date=6 November 2011|work=[[The Times of India]]|access-date=10 June 2012|archive-url=https://web.archive.org/web/20111109075136/http://articles.timesofindia.indiatimes.com/2011-11-06/news-and-interviews/30364066_1_tv-ad-campaign-music-label-album|archive-date=9 November 2011}}</ref> गुलशन कुमार आ हरिहरन के गावल ''हनुमान चालीसा'' के एगो प्रस्तुति नवंबर 2021 में पहिला भक्ति गीत आ [[यूट्यूब]] पर पहिला बेर बनल जवन 2 अरब व्यूज पार कइलस। वर्तमान में [[यूट्यूब]] पर भी इ सबसे ज्यादा देखल जाए वाला भारतीय म्यूजिक वीडियो बा।<ref>{{Cite web|url=https://infotonline.com/hanuman-chalisa-by-gulshan-kumar-crosses-1b-views/|title=Hanuman Chalisa by Gulshan Kumar crosses 1B views on YouTube, another World record made by T-series|date=27 May 2020|website=Infotonline|access-date=May 27, 2020|archive-date=20 September 2020|archive-url=https://web.archive.org/web/20200920180754/https://infotonline.com/hanuman-chalisa-by-gulshan-kumar-crosses-1b-views/|url-status=dead}}</ref> == संदर्भ == {{Reflist|33em}} === Bibliography === {{refbegin|30em}} * {{cite book |last1=de Bruyn |first1=Pippa |last2=Bain |first2=Keith |last3=Allardice |first3=David |last4=Joshi |first4=Shonar |title=Frommer's India |year=2010 |publisher=John Wiley and Sons |isbn=978-0-470-60264-5 |location=Hoboken, New Jersey |page=471}} * {{cite book |last1=Callewaert |first1=Winand M. |last2=Schilder |first2=Robert |title=Banaras: Vision of a Living Ancient Tradition |year=2000 |publisher=Hemkunt Press |isbn=9788170103028 |location=New Delhi, India |page=90}} * {{cite book |last=Chaturvedi |first=B.K. |title=Shri Hanuman Chalisa (Roman) |year=1994b |publisher=Diamond Pocket Books |location=New Delhi |isbn=81-7182-395-5}} * {{cite book |last1=Jones |first1=Constance |last2=Ryan |first2=James D. |isbn=978-0-8160-5458-9 |title=Encyclopedia of Hinduism |series=Encyclopedia of World Religions |location=New York |year=2007 |publisher=Infobase Publishing |page=456 |quote=It can be said without reservation that Tulsidas is the greatest poet to write in the Hindi language. Tulsidas was a Brahmin by birth and was believed to be a reincarnation of the author of the Sanskrit Ramayana, Valmiki.}} * {{cite book |last=Mehta |first=Pt. Vijay Shankar |title=Kripa Karahu Guru Dev Ki Naain |year=2007 |publisher=Radhakrishnan Prakashan |location=New Delhi |isbn=978-81-8361-041-4 |page=9 |edition=2nd}} * {{cite book |last=Misra |first=Munindra |title=Shri Hanuman Chalisa in English Rhyme with original text |year=2015 |publisher=Osmora Inc. |location=United States |isbn=9782765913702}} * {{cite book |last=Misra |first=Nityanand |author-link=Nityanand Misra |title=Mahāvīrī: Hanumān-Cālīsā Demystified |year=2015 |publisher=Niraamaya Publishing Services Pvt Ltd |location=Mumbai, India |isbn=9788193114407}} * {{cite book |last=Mitra |first=Swati |title=Good Earth Varanasi City Guide |publisher=Eicher Goodearth Limited |location=New Delhi, India |year=2002 |isbn=9788187780045 |page=216}} * {{cite book |last=Peebles |first=Patrick |title=Voices of South Asia: Essential Readings from Antiquity to the Present |publisher=M.E. Sharpe Inc. |location=United States |year=1986 |isbn=978-0-7656-3480-1 |page=216}} * {{cite web |last=Rambhadradas |author-link=Rambhadracharya |website=Jagadgururambhadracharya.org |date=8 June 1984 |publisher=Krishnadas Charitable Trust |location=New Delhi, India |language=hi |trans-title=Shri Hanuman Chalisa (with the Mahaviri commentary) |title= |script-title=hi:संकट तें हनुमान छुड़ावै। मन क्रम बचन ध्यान जो लावै |url=http://jagadgururambhadracharya.org/works/hcm/contents.php |access-date=29 May 2013 |archive-url=https://web.archive.org/web/20131117205618/http://jagadgururambhadracharya.org/works/hcm/contents.php |archive-date=17 November 2013}} * {{cite book |last=Rao |first=Cheeni |title=In Hanuman's Hands: A Memoir |year=2009 |publisher=Harper Collins Publishers |location=United States |isbn=978-0-06-073662-0 |page=393 |edition=First}} * {{cite book |last=Sahni |first=Bhisham |author-link=Bhisham Sahni |isbn=9788171789603 |title=Nilu, Nilima, Nilofara |location=New Delhi, India |language=hi |year=2000 |publisher=Rajkamal Prakashan Pvt Ltd |pages=78–80 |quote=हिन्दी का सौभाग्य है कि उसके काव्यकुंज की तुलसी-मंजरी की जैसी सुगंध संसार की साहित्य वाटिका में शायद कहीं नहीं। ... आकर्षण दोनों में अत्यधिक है अपने-अपने ढंग पर दोनों ही बहुत बड़े हैं, पर फिर भी सब तरफ़ से केवल काव्य के सौंदर्य पर विचार करने पर तुलसीदास ही बड़े ठहरते हैं – भाषा साहित्य में रवीन्द्रनाथ के संबंध में कहना पड़ता है कि भ्रम त्रुटियाँ मिल सकती हैं पर तुलसीदास के संबंध में कोई शायद ही मिले। ... और यही कारण है निराला जी तुलसीदास को कालिदास, व्यास, वाल्मीकि, होमर, गेटे और शेक्सपियर के समकक्ष रखकर उनके महत्त्व का आकलन करते हैं।}} * {{cite book |last=Subramanian |first=Vadakaymadam Krishnier |location=New Delhi, India |title=Hymns of Tulsidas |publisher=Abhinav Publications |year=2008 |isbn=9788170174967 |page=inside cover |quote=Famous classical singers like Paluskar, Anoop Jalota and MS Subbulakshmi have popularised Tulsidas's hymns among the people of India.}} {{refend}} [[श्रेणी:हिंदी कबितई]] [[श्रेणी:हिंदी भक्ति गीत]] [[श्रेणी:हिंदू भक्ति गीत]] 5bptpmtai23dkpc1cot2pm8pa4l1plq ट्रिपल सी 0 92823 797097 796306 2026-06-08T14:19:35Z SM7 3953 सफाई कइल गइल 797097 wikitext text/x-wiki '''ट्रिपल सी''' ({{Lang|en|CCC}}), पूरा नाँव '''कोर्स ऑन कंप्यूटर कॉन्सेप्ट्स''' (Course on Computer Concepts) एगो [[कंप्यूटर]] कोर्स हवे जे भारत में 'राष्ट्रीय इलेक्ट्रॉनिकी आ इन्फार्मेशन टेक्नालॉजी इंस्टिट्यूट' (NIELIT) द्वारा चलावल जाला। ई कोर्स 2 हप्ता के होला आ आम लोगन खाती [[इन्फॉर्मेशन टेक्नोलॉजी]] (IT) के जानकारी देवे खातिर डिजाइन कइल गइल हवे। एह तरीका के आम जानकारी के अलावा एकर उपयोग कंपटीशन एक्जाम सभ में भी बाटे आ कई अइसन नोकरी वाला परीक्षा बाड़ी स जिनहन में CCC परीक्षा पास होखल माँगल जाला। कोर्स ऑफलाइन आ ऑनलाइन दुनों तरीका से उपलब्ध बाटे आ हर महीना एकर परीक्षा करावल जाला जेह में परीक्षार्थी के 90 मिनट में 100 गो सवालन के जबाब देवे के होला। == आउटकम == कोर्स पूरा कइला के बाद कंप्यूटर के इस्तेमाल से अपना सर्विस/बिजनेस में चिट्ठी-पतरी तइयार करे, [[इंटरनेट]] ([[वेबसाइट|वेब]]) पर जानकारी देखे, [[ई मेल|ईमेल]] पावे आ भेजे, आपन बिजनेस प्रेजेंटेशन तइयार करे, छोट डाटाबेस तइयार करे नियर चीजन के बेसिक मकसद पूरा करे में सक्षम हो सके ला। ई छोट बिजनेस कम्युनिटी, गृहिणी नियन लोगन के कंप्यूटर के इस्तेमाल से आपन छोट खाता बनावे में मदद करेला आ [[इनफार्मेशन टेक्नोलॉजी]] के दुनिया के आनंद उठावे में सक्षम बनावे ला। एही से ई कोर्स थियरी की बजाय प्रेटीकल पर बेसी जोर दे के बनावल गइल हवे। == बाहरी कड़ी == * [https://student.nielit.gov.in/ NIELIT के इस्टूडेंट पोर्टल], जहाँ कोर्स के जानकारी हासिल कइल जा सके ला आ रजिस्ट्रेशन करावल जा सके ला। [[श्रेणी:कंप्यूटर कोर्स]] [[श्रेणी:इन्फॉर्मेशन टेक्नोलॉजी]] {{edu-stub}} {{comp-sci-stub}} 7gqocee7eok7eg4evmgntoh14a4bz45 797104 797097 2026-06-08T14:53:11Z SM7 3953 +विकिकड़ी जोड़ल गइल, बाहरी कड़ी जोड़ल गइल 797104 wikitext text/x-wiki '''ट्रिपल सी''' ({{Lang|en|CCC}}), पूरा नाँव '''कोर्स ऑन कंप्यूटर कॉन्सेप्ट्स''' (Course on Computer Concepts) एगो [[कंप्यूटर]] कोर्स हवे जे भारत में 'राष्ट्रीय इलेक्ट्रॉनिकी आ इन्फार्मेशन टेक्नालॉजी इंस्टिट्यूट' (NIELIT) द्वारा चलावल जाला। ई कोर्स 2 हप्ता के होला आ आम लोगन खाती [[इन्फॉर्मेशन टेक्नोलॉजी]] (IT) के जानकारी देवे खातिर डिजाइन कइल गइल हवे। एह तरीका के आम जानकारी के अलावा एकर उपयोग कंपटीशन एक्जाम सभ में भी बाटे आ कई अइसन नोकरी वाला परीक्षा बाड़ी स जिनहन में CCC परीक्षा पास होखल माँगल जाला। कोर्स ऑफलाइन आ ऑनलाइन ([[बीडियो लेक्चर]] के माध्यम से) दुनों तरीका से उपलब्ध बाटे आ हर महीना एकर परीक्षा करावल जाला जेह में परीक्षार्थी के 90 मिनट में 100 गो सवालन के जबाब देवे के होला। == आउटकम == कोर्स पूरा कइला के बाद कंप्यूटर के इस्तेमाल से अपना सर्विस/बिजनेस में चिट्ठी-पतरी तइयार करे, [[इंटरनेट]] ([[वेबसाइट|वेब]]) पर जानकारी देखे, [[ई मेल|ईमेल]] पावे आ भेजे, आपन बिजनेस प्रेजेंटेशन तइयार करे, छोट डाटाबेस तइयार करे नियर चीजन के बेसिक मकसद पूरा करे में सक्षम हो सके ला। ई छोट बिजनेस कम्युनिटी, गृहिणी नियन लोगन के कंप्यूटर के इस्तेमाल से आपन छोट खाता बनावे में मदद करेला आ [[इनफार्मेशन टेक्नोलॉजी]] के दुनिया के आनंद उठावे में सक्षम बनावे ला। एही से ई कोर्स थियरी की बजाय प्रेटीकल पर बेसी जोर दे के बनावल गइल हवे। == बाहरी कड़ी == * [https://student.nielit.gov.in/ NIELIT के इस्टूडेंट पोर्टल], जहाँ कोर्स के जानकारी हासिल कइल जा सके ला आ रजिस्ट्रेशन करावल जा सके ला। * [https://lms.nielit.gov.in/course/index.php?categoryid=2 NIELIT के पोर्टल पर CCC के पन्ना], पूरा कोर्स देखल जा सके ला [[बीडियो लेक्चर]] देखल जा सके लें। [[श्रेणी:कंप्यूटर कोर्स]] [[श्रेणी:इन्फॉर्मेशन टेक्नोलॉजी]] {{edu-stub}} {{comp-sci-stub}} 0syj7kqmxy8saoethjclooxthntxgig प्रयोगकर्ता वार्ता:Shiv Shilpi 3 93336 797096 765477 2026-06-08T13:11:02Z Shiv Shilpi 34150 /* */ जबाब 797096 wikitext text/x-wiki {| id="GeoPort-upper" width="100%" cellpadding="5" cellspacing="6" style="background:#FFFAFF; text-align: justify; border-style:ridge; border-width:1px; border-color:#A9A9A9;" |- |<div style="display:inline-block;margin-top:.1em; text-align:right; margin-bottom:.2em; border-bottom:0; font-weight:bold;"><big>Welcome! स्वागतम्!</big> [[File:Crystal Clear app ksmiletris.png|25px]]&nbsp;</div> राउर बहुत-बहुत स्वागत बा '''{{BASEPAGENAME}}''' जी ! {{#if: | {{{1}}} | }}<br/> <div style="float:right; <!--background:#F5F5DC;--> width:30%"> <!-- दाहिना साइडबार --> {| border="5" cellspacing="10" cellpadding="5" height="50" align=center border=0 style="background: transparent" |- | <big>'''ई जरूर पढ़ल जाय:'''</big> |- | style="background-color:#FFFFFF; border: solid 2px #FFFFFF; padding:10px 20px;" | [[विकिपीडिया:विकिपीडिया का ना हवे|विकिपीडिया का ना हवे?]] |- | style="background-color:#FFFFFF; border: solid 2px #FFFFFF; padding:10px 20px;" | [[विकिपीडिया:भोजपुरी में कइसे टाइप करब?|भोजपुरी में टाइपिंग]] |- | style="background-color:#FFFFFF; border: solid 1px #FFFFFF; padding:10px 20px;" | [[विकिपीडिया:सत्यापन जोग|प्रमाणित बात लिखीं]] |- | style="background-color:#FFFFFF; border: solid 1px #FFFFFF; padding:10px 20px;" | [[मदद:फुटनोट|संदर्भ कइसे जोड़ीं?]] |- | style="background-color:#FFFFFF; border: solid 1px #FFFFFF; padding:10px 20px;" | [[विकिपीडिया:नीति अउरी दिसानिर्देस|विकिनीति आ निर्देश]] |}</div> <!-- मुख्य पाठ --> '''{{#if: | {{{1}}} | {{BASEPAGENAME}} }} जी''', एह समय रउँआ [[विकिमीडिया फाउन्डेशन]] के परियोजना [[भोजपुरी]] [[विकिपीडिया]] पर बाड़ीं। भोजपुरी विकिपीडिया एगो मुक्त डिजिटल [[ज्ञानकोश]] हवे, जेवन अइसन भइया-बहिनी लोग मिल के लिखले बा जे ज्ञान बाँटे में बिस्वास करत बाटे। एह समय ए परियोजना में [[विशेष:ActiveUsers|{{NUMBEROFUSERS}} सदस्य]] लोग शामिल बाटे। ई बहुते खुशी क बाति बा कि रउँओं ए में शामिल हो गइल बाड़ीं। * पहिले से बनल [[विकिपीडिया:लेख|लेखवन]] में कौनो संपादन खाली टेस्ट करे खातिर मत करीं। कौनों तरह के परीक्षण <small>(प्रयोग या टेस्टिंग)</small> [[विकिपीडिया:अभ्यास पन्ना|अभ्यास पन्ना]] या [[Special:MyPage/sandbox|अपना अभ्यास पन्ना]] पर करीं। * [[विकिपीडिया:आपन परिचय कइसे देईं?|आपन परिचय]] आप संछेप में [[प्रयोगकर्ता:{{BASEPAGENAME}}|अपना सदस्य पन्ना]] पर दे सकत बानी। बहुत पर्सनल बात इहाँ मत लिखीं, न कौनों परचार वाली बात लिखीं। अपने खुद के बारे में लेख मत बनाईं। * दुसरा [[विकिपीडिया:चौपाल |सदस्य लोगन से बात]] करत समय, [[मदद:वार्ता पन्ना|बातचीत पन्ना]] पर सनेसा लिखले की बाद आपन [[विकिपीडिया:दसखत|दसखत]] <small>(हस्ताक्षर)</small> जरूर करीं। एकरा खातिर अंत में चार गो टेढ़का डैश (<nowiki>~~~~</nowiki>) लिख देंईं या टूलबार में [[File:Insert-signature2.svg|link=|alt=]] पर क्लिक करीं। * मदद चाहत होखीं त विकिपीडिया के [[विकिपीडिया:मदद|मदद पन्ना]] पर जाईं। <!-- फुटर के कड़ी सभ --> सीखे-समझे खातिर कुछ अउरी कड़ी नीचे दिहल जात बाटे: {| border="5" cellspacing="1" cellpadding="0" height="50" align=center border=0 style="background: transparent" | style="background-color:#FFFFFF; border: solid 2px #F2BDCD; padding:1px 10px;" | [[विकिपीडिया:स्वशिक्षा|शुरू से सीखीं]] |&nbsp;&nbsp; | style="background-color:#FFFFFF; border: solid 2px #F2BDCD; padding:1px 20px;" | [[मदद:संपादन|संपादन सीखीं]] |&nbsp;&nbsp; | style="background-color:#FFFFFF; border: solid 2px #F2BDCD; padding:1px 20px;" | [[विकिपीडिया:नया लेख कइसे सुरू करीं?|नया लेख]] | style="background-color:#FFFFFF; border: solid 2px #F2BDCD; padding:1px 20px;" | [[विकिपीडिया:अइसन लेख मना बाटे|लेख मनाहीं]] |&nbsp;&nbsp; | style="background-color:#FFFFFF; border: solid 2px #F2BDCD; padding:1px 20px;" | [[विकिपीडिया:पंचशील|पंचशील]] | style="background-color:#FFFFFF; border: solid 2px #F2BDCD; padding:1px 20px;" | [[विकिपीडिया:समुदाय पोर्टल|सदस्य समाज पन्ना]] |} |} -- [[प्रयोगकर्ता:नया सदस्य स्वागतकर्ता|नया सदस्य स्वागतकर्ता]] ([[प्रयोगकर्ता वार्ता:नया सदस्य स्वागतकर्ता|बात करीं]]) 19:08, 16 नवंबर 2023 (UTC) :बहुत बहुत धन्यवाद sir जी जानकारी देवे खातिर [[प्रयोगकर्ता:Shiv Shilpi|Shiv Shilpi]] ([[प्रयोगकर्ता वार्ता:Shiv Shilpi|बात करीं]]) 13:11, 8 जून 2026 (UTC) tr1lyjb4h5jn00m5ls2j8x9z90obar4 विकिपीडिया:आँकड़ा सभ/२०२६/जून 4 100967 797106 796877 2026-06-08T19:21:32Z NeechalBOT 7874 statistics 797106 wikitext text/x-wiki <!--- stats starts--->{{प्रयोगकर्ता:Neechalkaran/statnotice}}{| class="wikitable sortable" style="width:90%" |- ! Date(Time) ! Pages ! Articles ! Edits ! Users ! Files ! Activeusers {{User:Neechalkaran/template/daily |Date =५-६-२०२६ |Pages = 81762 |dPages = 21 |Articles = 9065 |dArticles = 6 |Edits = 793428 |dEdits = 62 |Files = 54 |dFiles = 0 |Users = 40031 |dUsers = 9 |Ausers = 55 |dAusers = 0 }} {{User:Neechalkaran/template/daily |Date =६-६-२०२६ |Pages = 81791 |dPages = 29 |Articles = 9073 |dArticles = 8 |Edits = 793679 |dEdits = 251 |Files = 54 |dFiles = 0 |Users = 40034 |dUsers = 3 |Ausers = 55 |dAusers = 0 }} {{User:Neechalkaran/template/daily |Date =८-६-२०२६ |Pages = 81811 |dPages = 20 |Articles = 9078 |dArticles = 5 |Edits = 793919 |dEdits = 240 |Files = 54 |dFiles = 0 |Users = 40046 |dUsers = 12 |Ausers = 55 |dAusers = 0 }} <!---Place new stats here---> |} <!--- stats ends---> m6iya3rz9ryooy3pkyirsm37b6cta5p भक्ति आंदोलन 0 101004 797105 796935 2026-06-08T19:21:19Z SM7 3953 बिस्तार कइल गइल 797105 wikitext text/x-wiki [[File:Meerabai (crop).jpg|thumb|[[कृष्ण]] भक्ती आंदोलन के मुख्य हिस्सा रहल बाड़ें। इनके एगो प्रमुख भक्त [[मीराबाई]] रहली (फोटो में देखावल)<ref name="smpandey">{{cite journal|author= SM Pandey |date= 1965 |title= Mīrābāī and Her Contributions to the Bhakti Movement |journal= History of Religions |volume= 5 |number= 1 |pages= 54–73|jstor= 1061803 |doi= 10.1086/462514 |s2cid= 162398500 }}</ref>]] '''भक्ति आंदोलन''' मध्यकालीन [[हिंदू धर्म]] के एगो महत्त्वपूर्ण धार्मिक आंदोलन रहे,{{sfnp|Schomer|McLeod|1987|p=1}} जेकर मकसद [[भक्ति]] के माध्यम से [[मोक्ष]] (मुक्ती) प्राप्ति के मार्ग अपना के<ref name="cbseindiatoday">{{Cite web|url=https://www.indiatoday.in/education-today/gk-current-affairs/story/-crashcourse-cbse-class-12-history-bhakti-movement-s-emergence-and-influence-1438286-2019-01-24|title=CBSE Class 12 History #CrashCourse: Bhakti movement's emergence and influence|last1=India Today Web Desk New|date=January 24, 2019|website=India Today}}</ref> समाज के सभ वर्गन तक धार्मिक सुधार पहुँचावल रहे। एह आंदोलन के शुरुआत 6वीं सदी ईस्वी में{{sfn|Hawley|2015|p=87}}<ref>{{Cite book |last=Padmaja |first=T. |url=https://books.google.com/books?id=pzgaS1wRnl8C&dq=bhakti+movement+tamilakam&pg=RA1-PA37 |title=Temples of Kr̥ṣṇa in South India: History, Art, and Traditions in Tamil nāḍu |date=2002 |publisher=Abhinav |isbn=978-81-7017-398-4 |language=en}}</ref> [[तमिलकम]] (प्राचीन तमिल क्षेत्र) में भइल मानल जाला। दक्खिन भारत में शुरुआती मध्यकाल के दौरान वैष्णव आलवार आ शैव नायनार संत लोग के भक्ति-प्रधान कविता आ उपदेशन के माध्यम से एह आंदोलन के बिसेस पहिचान मिलल।{{sfnp|Schomer|McLeod|1987|p=1}} बाद में ई आंदोलन धीरे-धीरे उत्तर दिशा में फइलल आ भारत के बाकी अलग-अलग क्षेत्रन तक पहुँच गइल। 15वीं सदी के बाद भक्ति आंदोलन पूरबी आ उत्तरी भारत में तेजी से फइलल आ 15वीं से 17वीं सदी ईस्वी के बीच अपना चरम पर पहुँच गइल। ई आंदोलन धार्मिक आ सामाजिक जीवन पर गम्हीर प्रभाव डललस आ भक्ति के व्यक्तिगत आ सहज मार्ग के लोकप्रिय बनवलस।{{sfnp|Schomer|McLeod|1987|pp=1-2}} भक्ति आंदोलन भारतीय उपमहादीप के अलग-अलग क्षेत्रन में अलग-अलग हिंदू देवी-देवतन के केंद्र में रख के बिकसित भइल। एह आंदोलन के कुछ प्रमुख धारा सभ में [[वैष्णव संप्रदाय|वैष्णव मत]] ([[विष्णु]] के उपासना), [[शैव मत]] ([[शिव]] के उपासना), [[शाक्त संप्रदाय|शाक्त मत]] ([[शक्ति (देवी)|शक्ति]] देवी के उपासना) आ [[स्मार्त|स्मार्त परंपरा]] शामिल रहल।<ref>{{cite book |first=Lance |last=Nelson |year=2007 |title=An Introductory Dictionary of Theology and Religious Studies |editor1-first=Orlando O. |editor1-last=Espín |editor2-first=James B. |editor2-last=Nickoloff |publisher=Liturgical Press |isbn=978-0814658567 |pages=562–563}}</ref><ref>{{cite book |first=S. S. |last=Kumar |year=2010 |title=Bhakti – the Yoga of Love |publisher=LIT |location=Münster |isbn=978-3643501301 |pages=35–36}}</ref><ref name="donigerbrit">{{cite encyclopedia |first=Wendy |last=Doniger |year=2009 |url=http://www.britannica.com/EBchecked/topic/63933/bhakti |title=Bhakti |encyclopedia=Encyclopædia Britannica}}</ref><ref name="Surinder 1999">{{cite book|last1=Johar|first1=Surinder|title=Guru Gobind Singh: A Multi-faceted Personality|date=1999|publisher=MD Publications|isbn=978-8-175-33093-1|page=89}}</ref> भक्ति आंदोलन के संत आ प्रचारक लोग अपना उपदेश आ रचना सभ खातिर लोकल भाषा-बोली सभ के इस्तेमाल कइल लोग, जवना से कि ओह लोगन के संदेश आम जनता तक आसानी से पहुँच सके। एह आंदोलन के प्रेरणा अनेक कवि-संतन से मिलल, जिनकर विचारधारा बहुत विविध किसिम के रहल। एहमें द्वैत दर्शन के ईश्वरवादी मत से लेके [[अद्वैत वेदांत]] के पूर्ण अद्वैतवादी दर्शन तक के विचार शामिल रहल।{{sfnp|Schomer|McLeod|1987|p=2}}<ref name=novetzke>{{cite journal|author= Christian Novetzke |date= 2007 |title= Bhakti and Its Public |journal= International Journal of Hindu Studies |volume= 11 |number= 3 |pages= 255–272|jstor= 25691067 |doi= 10.1007/s11407-008-9049-9 |s2cid= 144065168 }}</ref> ई सगरी संत लोग भक्ति के माध्यम से आध्यात्मिक उन्नति के मार्ग बतावल लोग। परंपरागत रूप से भक्ति आंदोलन के हिंदू धर्म के एगो प्रभावशाली सामाजिक सुधार आंदोलन मानल गइल बा, काहें से कि ई जनम, जाति भा लिंग से ऊपर उठ के हर ब्यक्ती खातिर आध्यात्मिकता के आपन निजी मार्ग उपलब्ध करावे ला।{sfnp|Schomer|McLeod|1987|pp=1–2}} हालाँकि, कुछ आधुनिक बिद्वान लोग एह बात पर सवाल उठावेलन कि भक्ति आंदोलन वास्तव में सामाजिक सुधार भा विद्रोह के रूप में उभरल रहे कि ना। एह लोगन के हिसाब से, भक्ति आंदोलन के प्राचीन वैदिक परंपरा के दुबारा जिंदा होखे (रिवाइवल), पुनर्व्याख्या (रिवार्किंग) आ नया सामाजिक संदर्भ में पुनर्स्थापन (रीकॉन्टेक्स्चुअलाइजेशन) के रूप में समझल-मानल जा सकेला।{{sfnp|Pechilis Prentiss|2014|pages= 15-16}} {{clear}} == इहो देखल जाय == * [[सूफीवाद]] == टीका-टिप्पणी == {{reflist|group=note}} {{notelist}} == फुटनोट आ संदर्भ == {{Reflist|29em}} == किताबी स्रोत आ ग्रंथ सूची == {{refbegin|29em}} {{refend}} == बाहरी कड़ी == {{Commons category|Bhakti movement}} {{Wikiquote|Bhakti movement}} * [http://sites.fas.harvard.edu/~fc12/Bibliography/09_Bhakti_Bibliography.html Bhakti bibliography] {{Webarchive|url=https://web.archive.org/web/20160304123833/http://sites.fas.harvard.edu/~fc12/Bibliography/09_Bhakti_Bibliography.html |date=4 March 2016 }}, Harvard University Archive (2001) * [[Wikisource: The Complete Works of Swami Vivekananda/Volume 3/Bhakti-Yoga/Definition of Bhakti|Definition of Bhakti]], Swami Vivekananda, Wikisource {{हिंदू धर्म बिसय}} [[श्रेणी:भक्ति आंदोलन| ]] [[श्रेणी:हिंदू धर्म के इतिहास]] [[श्रेणी:हिंदू धर्म]] [[श्रेणी:सामाजिक आंदोलन]] {{hinduism-stub}} bqcjiiys07kohoo7knefeq2kj0a5o7s 797107 797105 2026-06-08T19:21:48Z SM7 3953 सुधार कइल गइल 797107 wikitext text/x-wiki [[File:Meerabai (crop).jpg|thumb|[[कृष्ण]] भक्ती आंदोलन के मुख्य हिस्सा रहल बाड़ें। इनके एगो प्रमुख भक्त [[मीराबाई]] रहली (फोटो में देखावल)<ref name="smpandey">{{cite journal|author= SM Pandey |date= 1965 |title= Mīrābāī and Her Contributions to the Bhakti Movement |journal= History of Religions |volume= 5 |number= 1 |pages= 54–73|jstor= 1061803 |doi= 10.1086/462514 |s2cid= 162398500 }}</ref>]] '''भक्ति आंदोलन''' मध्यकालीन [[हिंदू धर्म]] के एगो महत्त्वपूर्ण धार्मिक आंदोलन रहे,{{sfnp|Schomer|McLeod|1987|p=1}} जेकर मकसद [[भक्ति]] के माध्यम से [[मोक्ष]] (मुक्ती) प्राप्ति के मार्ग अपना के<ref name="cbseindiatoday">{{Cite web|url=https://www.indiatoday.in/education-today/gk-current-affairs/story/-crashcourse-cbse-class-12-history-bhakti-movement-s-emergence-and-influence-1438286-2019-01-24|title=CBSE Class 12 History #CrashCourse: Bhakti movement's emergence and influence|last1=India Today Web Desk New|date=January 24, 2019|website=India Today}}</ref> समाज के सभ वर्गन तक धार्मिक सुधार पहुँचावल रहे। एह आंदोलन के शुरुआत 6वीं सदी ईस्वी में{{sfn|Hawley|2015|p=87}}<ref>{{Cite book |last=Padmaja |first=T. |url=https://books.google.com/books?id=pzgaS1wRnl8C&dq=bhakti+movement+tamilakam&pg=RA1-PA37 |title=Temples of Kr̥ṣṇa in South India: History, Art, and Traditions in Tamil nāḍu |date=2002 |publisher=Abhinav |isbn=978-81-7017-398-4 |language=en}}</ref> [[तमिलकम]] (प्राचीन तमिल क्षेत्र) में भइल मानल जाला। दक्खिन भारत में शुरुआती मध्यकाल के दौरान वैष्णव आलवार आ शैव नायनार संत लोग के भक्ति-प्रधान कविता आ उपदेशन के माध्यम से एह आंदोलन के बिसेस पहिचान मिलल।{{sfnp|Schomer|McLeod|1987|p=1}} बाद में ई आंदोलन धीरे-धीरे उत्तर दिशा में फइलल आ भारत के बाकी अलग-अलग क्षेत्रन तक पहुँच गइल। 15वीं सदी के बाद भक्ति आंदोलन पूरबी आ उत्तरी भारत में तेजी से फइलल आ 15वीं से 17वीं सदी ईस्वी के बीच अपना चरम पर पहुँच गइल। ई आंदोलन धार्मिक आ सामाजिक जीवन पर गम्हीर प्रभाव डललस आ भक्ति के व्यक्तिगत आ सहज मार्ग के लोकप्रिय बनवलस।{{sfnp|Schomer|McLeod|1987|pp=1-2}} भक्ति आंदोलन भारतीय उपमहादीप के अलग-अलग क्षेत्रन में अलग-अलग हिंदू देवी-देवतन के केंद्र में रख के बिकसित भइल। एह आंदोलन के कुछ प्रमुख धारा सभ में [[वैष्णव संप्रदाय|वैष्णव मत]] ([[विष्णु]] के उपासना), [[शैव मत]] ([[शिव]] के उपासना), [[शाक्त संप्रदाय|शाक्त मत]] ([[शक्ति (देवी)|शक्ति]] देवी के उपासना) आ [[स्मार्त|स्मार्त परंपरा]] शामिल रहल।<ref>{{cite book |first=Lance |last=Nelson |year=2007 |title=An Introductory Dictionary of Theology and Religious Studies |editor1-first=Orlando O. |editor1-last=Espín |editor2-first=James B. |editor2-last=Nickoloff |publisher=Liturgical Press |isbn=978-0814658567 |pages=562–563}}</ref><ref>{{cite book |first=S. S. |last=Kumar |year=2010 |title=Bhakti – the Yoga of Love |publisher=LIT |location=Münster |isbn=978-3643501301 |pages=35–36}}</ref><ref name="donigerbrit">{{cite encyclopedia |first=Wendy |last=Doniger |year=2009 |url=http://www.britannica.com/EBchecked/topic/63933/bhakti |title=Bhakti |encyclopedia=Encyclopædia Britannica}}</ref><ref name="Surinder 1999">{{cite book|last1=Johar|first1=Surinder|title=Guru Gobind Singh: A Multi-faceted Personality|date=1999|publisher=MD Publications|isbn=978-8-175-33093-1|page=89}}</ref> भक्ति आंदोलन के संत आ प्रचारक लोग अपना उपदेश आ रचना सभ खातिर लोकल भाषा-बोली सभ के इस्तेमाल कइल लोग, जवना से कि ओह लोगन के संदेश आम जनता तक आसानी से पहुँच सके। एह आंदोलन के प्रेरणा अनेक कवि-संतन से मिलल, जिनकर विचारधारा बहुत विविध किसिम के रहल। एहमें द्वैत दर्शन के ईश्वरवादी मत से लेके [[अद्वैत वेदांत]] के पूर्ण अद्वैतवादी दर्शन तक के विचार शामिल रहल।{{sfnp|Schomer|McLeod|1987|p=2}}<ref name=novetzke>{{cite journal|author= Christian Novetzke |date= 2007 |title= Bhakti and Its Public |journal= International Journal of Hindu Studies |volume= 11 |number= 3 |pages= 255–272|jstor= 25691067 |doi= 10.1007/s11407-008-9049-9 |s2cid= 144065168 }}</ref> ई सगरी संत लोग भक्ति के माध्यम से आध्यात्मिक उन्नति के मार्ग बतावल लोग। परंपरागत रूप से भक्ति आंदोलन के हिंदू धर्म के एगो प्रभावशाली सामाजिक सुधार आंदोलन मानल गइल बा, काहें से कि ई जनम, जाति भा लिंग से ऊपर उठ के हर ब्यक्ती खातिर आध्यात्मिकता के आपन निजी मार्ग उपलब्ध करावे ला।{{sfnp|Schomer|McLeod|1987|pp=1–2}} हालाँकि, कुछ आधुनिक बिद्वान लोग एह बात पर सवाल उठावेलन कि भक्ति आंदोलन वास्तव में सामाजिक सुधार भा विद्रोह के रूप में उभरल रहे कि ना। एह लोगन के हिसाब से, भक्ति आंदोलन के प्राचीन वैदिक परंपरा के दुबारा जिंदा होखे (रिवाइवल), पुनर्व्याख्या (रिवार्किंग) आ नया सामाजिक संदर्भ में पुनर्स्थापन (रीकॉन्टेक्स्चुअलाइजेशन) के रूप में समझल-मानल जा सकेला।{{sfnp|Pechilis Prentiss|2014|pages= 15-16}} {{clear}} == इहो देखल जाय == * [[सूफीवाद]] == टीका-टिप्पणी == {{reflist|group=note}} {{notelist}} == फुटनोट आ संदर्भ == {{Reflist|29em}} == किताबी स्रोत आ ग्रंथ सूची == {{refbegin|29em}} {{refend}} == बाहरी कड़ी == {{Commons category|Bhakti movement}} {{Wikiquote|Bhakti movement}} * [http://sites.fas.harvard.edu/~fc12/Bibliography/09_Bhakti_Bibliography.html Bhakti bibliography] {{Webarchive|url=https://web.archive.org/web/20160304123833/http://sites.fas.harvard.edu/~fc12/Bibliography/09_Bhakti_Bibliography.html |date=4 March 2016 }}, Harvard University Archive (2001) * [[Wikisource: The Complete Works of Swami Vivekananda/Volume 3/Bhakti-Yoga/Definition of Bhakti|Definition of Bhakti]], Swami Vivekananda, Wikisource {{हिंदू धर्म बिसय}} [[श्रेणी:भक्ति आंदोलन| ]] [[श्रेणी:हिंदू धर्म के इतिहास]] [[श्रेणी:हिंदू धर्म]] [[श्रेणी:सामाजिक आंदोलन]] {{hinduism-stub}} mtzoc1vj4m8kl547lpfsyuq5pyxvcfx 797108 797107 2026-06-08T19:45:01Z SM7 3953 अंग्रेजी विकिपीडिया से अनुबाद क के बिस्तार कइल गइल 797108 wikitext text/x-wiki [[File:Meerabai (crop).jpg|thumb|[[कृष्ण]] भक्ती आंदोलन के मुख्य हिस्सा रहल बाड़ें। इनके एगो प्रमुख भक्त [[मीराबाई]] रहली (फोटो में देखावल)<ref name="smpandey">{{cite journal|author= SM Pandey |date= 1965 |title= Mīrābāī and Her Contributions to the Bhakti Movement |journal= History of Religions |volume= 5 |number= 1 |pages= 54–73|jstor= 1061803 |doi= 10.1086/462514 |s2cid= 162398500 }}</ref>]] '''भक्ति आंदोलन''' मध्यकालीन [[हिंदू धर्म]] के एगो महत्त्वपूर्ण धार्मिक आंदोलन रहे,{{sfnp|Schomer|McLeod|1987|p=1}} जेकर मकसद [[भक्ति]] के माध्यम से [[मोक्ष]] (मुक्ती) प्राप्ति के मार्ग अपना के<ref name="cbseindiatoday">{{Cite web|url=https://www.indiatoday.in/education-today/gk-current-affairs/story/-crashcourse-cbse-class-12-history-bhakti-movement-s-emergence-and-influence-1438286-2019-01-24|title=CBSE Class 12 History #CrashCourse: Bhakti movement's emergence and influence|last1=India Today Web Desk New|date=January 24, 2019|website=India Today}}</ref> समाज के सभ वर्गन तक धार्मिक सुधार पहुँचावल रहे। एह आंदोलन के शुरुआत 6वीं सदी ईस्वी में{{sfn|Hawley|2015|p=87}}<ref>{{Cite book |last=Padmaja |first=T. |url=https://books.google.com/books?id=pzgaS1wRnl8C&dq=bhakti+movement+tamilakam&pg=RA1-PA37 |title=Temples of Kr̥ṣṇa in South India: History, Art, and Traditions in Tamil nāḍu |date=2002 |publisher=Abhinav |isbn=978-81-7017-398-4 |language=en}}</ref> [[तमिलकम]] (प्राचीन तमिल क्षेत्र) में भइल मानल जाला। दक्खिन भारत में शुरुआती मध्यकाल के दौरान वैष्णव आलवार आ शैव नायनार संत लोग के भक्ति-प्रधान कविता आ उपदेशन के माध्यम से एह आंदोलन के बिसेस पहिचान मिलल।{{sfnp|Schomer|McLeod|1987|p=1}} बाद में ई आंदोलन धीरे-धीरे उत्तर दिशा में फइलल आ भारत के बाकी अलग-अलग क्षेत्रन तक पहुँच गइल। 15वीं सदी के बाद भक्ति आंदोलन पूरबी आ उत्तरी भारत में तेजी से फइलल आ 15वीं से 17वीं सदी ईस्वी के बीच अपना चरम पर पहुँच गइल। ई आंदोलन धार्मिक आ सामाजिक जीवन पर गम्हीर प्रभाव डललस आ भक्ति के व्यक्तिगत आ सहज मार्ग के लोकप्रिय बनवलस।{{sfnp|Schomer|McLeod|1987|pp=1-2}} भक्ति आंदोलन भारतीय उपमहादीप के अलग-अलग क्षेत्रन में अलग-अलग हिंदू देवी-देवतन के केंद्र में रख के बिकसित भइल। एह आंदोलन के कुछ प्रमुख धारा सभ में [[वैष्णव संप्रदाय|वैष्णव मत]] ([[विष्णु]] के उपासना), [[शैव मत]] ([[शिव]] के उपासना), [[शाक्त संप्रदाय|शाक्त मत]] ([[शक्ति (देवी)|शक्ति]] देवी के उपासना) आ [[स्मार्त|स्मार्त परंपरा]] शामिल रहल।<ref>{{cite book |first=Lance |last=Nelson |year=2007 |title=An Introductory Dictionary of Theology and Religious Studies |editor1-first=Orlando O. |editor1-last=Espín |editor2-first=James B. |editor2-last=Nickoloff |publisher=Liturgical Press |isbn=978-0814658567 |pages=562–563}}</ref><ref>{{cite book |first=S. S. |last=Kumar |year=2010 |title=Bhakti – the Yoga of Love |publisher=LIT |location=Münster |isbn=978-3643501301 |pages=35–36}}</ref><ref name="donigerbrit">{{cite encyclopedia |first=Wendy |last=Doniger |year=2009 |url=http://www.britannica.com/EBchecked/topic/63933/bhakti |title=Bhakti |encyclopedia=Encyclopædia Britannica}}</ref><ref name="Surinder 1999">{{cite book|last1=Johar|first1=Surinder|title=Guru Gobind Singh: A Multi-faceted Personality|date=1999|publisher=MD Publications|isbn=978-8-175-33093-1|page=89}}</ref> भक्ति आंदोलन के संत आ प्रचारक लोग अपना उपदेश आ रचना सभ खातिर लोकल भाषा-बोली सभ के इस्तेमाल कइल लोग, जवना से कि ओह लोगन के संदेश आम जनता तक आसानी से पहुँच सके। एह आंदोलन के प्रेरणा अनेक कवि-संतन से मिलल, जिनकर विचारधारा बहुत विविध किसिम के रहल। एहमें द्वैत दर्शन के ईश्वरवादी मत से लेके [[अद्वैत वेदांत]] के पूर्ण अद्वैतवादी दर्शन तक के विचार शामिल रहल।{{sfnp|Schomer|McLeod|1987|p=2}}<ref name=novetzke>{{cite journal|author= Christian Novetzke |date= 2007 |title= Bhakti and Its Public |journal= International Journal of Hindu Studies |volume= 11 |number= 3 |pages= 255–272|jstor= 25691067 |doi= 10.1007/s11407-008-9049-9 |s2cid= 144065168 }}</ref> ई सगरी संत लोग भक्ति के माध्यम से आध्यात्मिक उन्नति के मार्ग बतावल लोग। परंपरागत रूप से भक्ति आंदोलन के हिंदू धर्म के एगो प्रभावशाली सामाजिक सुधार आंदोलन मानल गइल बा, काहें से कि ई जनम, जाति भा लिंग से ऊपर उठ के हर ब्यक्ती खातिर आध्यात्मिकता के आपन निजी मार्ग उपलब्ध करावे ला।{{sfnp|Schomer|McLeod|1987|pp=1–2}} हालाँकि, कुछ आधुनिक बिद्वान लोग एह बात पर सवाल उठावेलन कि भक्ति आंदोलन वास्तव में सामाजिक सुधार भा विद्रोह के रूप में उभरल रहे कि ना। एह लोगन के हिसाब से, भक्ति आंदोलन के प्राचीन वैदिक परंपरा के दुबारा जिंदा होखे (रिवाइवल), पुनर्व्याख्या (रिवार्किंग) आ नया सामाजिक संदर्भ में पुनर्स्थापन (रीकॉन्टेक्स्चुअलाइजेशन) के रूप में समझल-मानल जा सकेला।{{sfnp|Pechilis Prentiss|2014|pages= 15-16}} == इतिहास == === प्रमुख लोग === भक्ति आंदोलन के दौरान क्षेत्रीय भाषा सभ में हिंदू साहित्य के उल्लेखजोग विकास भइल, खासकर भक्ति कविता आ भजन-संगीत के रूप में। एह साहित्यिक परंपरा में आलवार आ नायनार संतन के रचना, आंडाल, बसव, भक्त पीपा, अल्लम प्रभु, अक्का महादेवी, वल्लभाचार्य, विट्ठलनाथ (गुसाईंजी), कबीर, गुरु नानक, तुलसीदास, नाभादास, घनानंद, रामानंद, रैदास, श्रीपादराज, व्यासतीर्थ, पुरंदर दास, कनकदास, विजय दास, वृंदावन के षट् गोस्वामी, रसखान, जयदेव, नामदेव, एकनाथ, तुकाराम, मीराबाई, रामप्रसाद सेन, शंकरदेव, नरसिंह मेहता, गंगासती आ चैतन्य महाप्रभु जइसन संतन के उपदेश आ रचना शामिल बाड़ी स। आसाम में शंकरदेव के रचना में क्षेत्रीय भाषा पर विशेष जोर दिहल गइल, हालाँकि, एकरे बावजूद [[ब्रजावली]] नाँव के एगो आर्टिफिशियल साहित्यिक भाषा के विकास भइल। ब्रजावली मध्यकालीन मैथिली आ असमिया के मिलजुल रूप रहे। ई भाषा स्थानीय लोग खातिर आसानी से समझे लायक रहे, जे भक्ति आंदोलन के समावेशी भावना के अनुरूप रहे, जबकि एकरे चलते रचना सभ के साहित्यिक गरिमा भी बरकरार रहल। ठीक एही तरीका से, एगो अउरी आर्टिफिशियल साहित्यिक भाषा [[ब्रजबुली]] के लोकप्रिय बनावे के श्रेय [[विद्यापति]] के दिहल जाला। बाद में मध्यकाल में ओड़िशा के कई लेखक आ बंगाल के पुनर्जागरण काल के साहित्यकार लोग भी ब्रजबुली के अपनवलें। एह प्रकार भक्ति आंदोलन खाली धार्मिक चेतना के प्रसार तक सीमित ना रहल, बल्कि क्षेत्रीय भाषा, साहित्य आ सांस्कृतिक अभिव्यक्ति के विकास में भी महत्त्वपूर्ण योगदान दिहलस। 7वीं से 10वीं सदी के बीच के कुछ शुरुआती लेखक आ संत, जिनकर रचना बाद के कवि-संत आधारित भक्ति आंदोलन पर गहिर प्रभाव डाललस, ओहमें संबंदर, तिरुनावुक्करसर, सुंदरर, नम्मालवार, आदि शंकराचार्य, मणिक्कवाचकर आ नाथमुनि के नाम प्रमुख बा। एह लोग के उपदेश आ रचना दक्षिण भारत में भक्ति परंपरा के मजबूत आधार प्रदान कइलस। आगे चल के 11वीं आ 12वीं सदी में कई गो विद्वान संत लोग वेदांत परंपरा के भीतरें अलग-अलग दार्शनिक मत विकसित कइलें, जिनहन के मध्यकालीन भक्ति परंपरा पर बहुत प्रभाव पड़ल। एह विद्वानन में रामानुज, मध्वाचार्य, वल्लभाचार्य आ निंबार्क प्रमुख रहलें। एह लोग के विचारधारा ईश्वरवादी द्वैतवाद, विशिष्ट अद्वैत आ पूर्ण अद्वैत जइसन अलग-अलग दार्शनिक दृष्टिकोणन के प्रतिनिधित्व करे ला। भक्ति आंदोलन के दौरान कई महत्त्वपूर्ण ग्रंथन के विभिन्न भारतीय भाषा सभ में अनुबादो भइल। उदाहरण खातिर, आदि शंकराचार्य द्वारा संस्कृत में रचल सौंदर्य लहरी के 12वीं सदी में विरै कविराज पंडितर तमिल में अनुबाद कइलें, जेकर नाम अभिरामी पाडल रखल गइल। एही तरह, वाल्मीकि के संस्कृत में लिखल रामायण के पहिलका इंडो-आर्य भाषा में अनुबाद [[माधव कंदली]] द्वारा असमिया भाषा में कइल गइल, जे ''सप्तकांड रामायण'' के नाम से प्रसिद्ध बा। [[शांडिल्य]] आ [[नारद]] के रचल बतावल जाये वाला दू गो प्रसिद्ध भक्ति ग्रंथ — ''शांडिल्य भक्ति सूत्र'' आ ''नारद भक्ति सूत्र'' — भक्ति साहित्य के महत्त्वपूर्ण रचना मानल जालीं। हालाँकि, आधुनिक विद्वान लोग एह ग्रंथन के रचना काल लगभग 12वीं सदी के मानेलें। {{clear}} == इहो देखल जाय == * [[सूफीवाद]] == टीका-टिप्पणी == {{reflist|group=note}} {{notelist}} == फुटनोट आ संदर्भ == {{Reflist|29em}} == किताबी स्रोत आ ग्रंथ सूची == {{refbegin|29em}} {{refend}} == बाहरी कड़ी == {{Commons category|Bhakti movement}} {{Wikiquote|Bhakti movement}} * [http://sites.fas.harvard.edu/~fc12/Bibliography/09_Bhakti_Bibliography.html Bhakti bibliography] {{Webarchive|url=https://web.archive.org/web/20160304123833/http://sites.fas.harvard.edu/~fc12/Bibliography/09_Bhakti_Bibliography.html |date=4 March 2016 }}, Harvard University Archive (2001) * [[Wikisource: The Complete Works of Swami Vivekananda/Volume 3/Bhakti-Yoga/Definition of Bhakti|Definition of Bhakti]], Swami Vivekananda, Wikisource {{हिंदू धर्म बिसय}} [[श्रेणी:भक्ति आंदोलन| ]] [[श्रेणी:हिंदू धर्म के इतिहास]] [[श्रेणी:हिंदू धर्म]] [[श्रेणी:सामाजिक आंदोलन]] {{hinduism-stub}} kxgq9vns0nb9ufcgp56cl5kjvbuh6qc श्रेणी:लेख जिनहन में हिंदी-भाषा के स्रोत बाटे (hi) 14 101015 797100 2026-06-08T14:27:35Z SM7 3953 पन्ना बनावल गइल "{{Non-Bhojpuri-language sources category}}" के साथ 797100 wikitext text/x-wiki {{Non-Bhojpuri-language sources category}} 6co26q4quk3s6kll374dcojr2uyd9v0 विद्यालय 0 101016 797110 2026-06-08T19:56:27Z SM7 3953 पन्ना [[इस्कूल]] पर अनुप्रेषित कइल गइल 797110 wikitext text/x-wiki #REDIRECT [[इस्कूल]] pzjcdddn4t6hs6mfk1ankjbbkwrmr8f 797111 797110 2026-06-08T19:56:43Z SM7 3953 Added {{[[:Template:R from alternative name|R from alternative name]]}} tag to redirect 797111 wikitext text/x-wiki #REDIRECT [[इस्कूल]] {{Redirect category shell| {{R from alternative name}} }} j0qp09safi5twdn8m0sixa0fdncpu3l टेम्पलेट:Excerpt 10 101017 797114 2018-11-30T11:54:56Z en>Sophivorus 0 Create template for [[Wikipedia:Cascading content]]. Will continue working on this asap 797114 wikitext text/x-wiki <includeonly><div class="excerpt-block"><div class="noprint rellink"><span style="font-size: smaller;">This section is transcluded from </span> ''[[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2|}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|#{{{section|{{{2|}}}}}}}}]]''</div><div class="excerpt"> {{#if:{{{fragmento|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragmento|}}}}}|{{#if:{{{section|{{{2|}}}}}}|{{#lsth:{{{article|{{{1}}}}}}|{{{section|{{{2|}}}}}}}}|{{#invoke:Excerpt|lead|refs={{{refs|true}}}}}}}}}</div></includeonly><noinclude>{{Documentation}}</noinclude> fnvll6hqfq8uz3qbt25mas54hj0vzq6 797115 797114 2018-11-30T12:22:37Z en>Sophivorus 0 797115 wikitext text/x-wiki <includeonly><div class="excerpt-block"><div class="noprint rellink"><span style="font-size: smaller;">This section is transcluded from </span> ''[[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2|}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|#{{{section|{{{2|}}}}}}}}]]''</div><div class="excerpt"> {{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment|}}}}}|{{#if:{{{section|{{{2|}}}}}}|{{#lsth:{{{article|{{{1}}}}}}|{{{section|{{{2|}}}}}}}}|{{#invoke:Excerpt|lead|refs={{{refs|true}}}}}}}}}</div></includeonly><noinclude>{{Documentation}}</noinclude> 86wpv6xmcxjurxzetgxqszm1pxg9a0l 797116 797115 2018-11-30T12:28:35Z en>Sophivorus 0 797116 wikitext text/x-wiki <includeonly><div class="excerpt-block"><div class="noprint rellink"><span style="font-size: smaller;">This section is transcluded from </span> ''[[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2|}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|#{{{section|{{{2|}}}}}}}}]]''</div><div class="excerpt"> {{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment|}}}}}|{{#if:{{{section|{{{2|}}}}}}|{{#lsth:{{{article|{{{1}}}}}}|{{{section|{{{2|}}}}}}}}|{{#invoke:Excerpt|lead|refs={{{references|true}}}}}}}}}</div></includeonly><noinclude>{{Documentation}}</noinclude> 0ob4leyvz6bbyj2ffkdyoeigfduup6d 797117 797116 2019-11-13T12:50:16Z en>Sophivorus 0 Update template 797117 wikitext text/x-wiki <noinclude><div class="excerpt-block">{{hatnote|1=This section is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span class="mw-editsection-bracket">]</span></span>|selfref=yes}}<div class="excerpt">{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment|}}}}}|{{#if:{{{section|{{{2|}}}}}}|{{#lsth:{{{article|{{{1}}}}}}|{{{section|{{{2|}}}}}}}}|{{#invoke:Excerpt|lead|refs={{{references|true}}}}}}}}}</div></div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Wikipedia:Articles with extracts]]}}</noinclude><noinclude>{{Documentation}}</noinclude> f7gsn0g9pq7te0lfd9qrq15p4prxj09 797118 797117 2019-11-13T12:50:53Z en>Sophivorus 0 797118 wikitext text/x-wiki <includeonly><div class="excerpt-block">{{hatnote|1=This section is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span class="mw-editsection-bracket">]</span></span>|selfref=yes}}<div class="excerpt">{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment|}}}}}|{{#if:{{{section|{{{2|}}}}}}|{{#lsth:{{{article|{{{1}}}}}}|{{{section|{{{2|}}}}}}}}|{{#invoke:Excerpt|lead|refs={{{references|true}}}}}}}}}</div></div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Wikipedia:Articles with extracts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> jm98pi9f8yvzissyawtdghos8nh9uz7 797119 797118 2019-11-13T12:52:16Z en>Sophivorus 0 797119 wikitext text/x-wiki <includeonly><div class="excerpt-block">{{hatnote|1=This section is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span class="mw-editsection-bracket">]</span></span>''|selfref=yes}}<div class="excerpt">{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment|}}}}}|{{#if:{{{section|{{{2|}}}}}}|{{#lsth:{{{article|{{{1}}}}}}|{{{section|{{{2|}}}}}}}}|{{#invoke:Excerpt|lead|refs={{{references|true}}}}}}}}}</div></div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Wikipedia:Articles with extracts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> iuhte47zv5hxjgds3k1ghnt75w7jik2 797120 797119 2019-11-13T13:23:58Z en>Sophivorus 0 797120 wikitext text/x-wiki <includeonly><div class="excerpt-block">{{hatnote|1=This section is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span class="mw-editsection-bracket">]</span></span>''|selfref=yes}}<div class="excerpt">{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment|}}}}}|{{#if:{{{section|{{{2|}}}}}}|{{#lsth:{{{article|{{{1}}}}}}|{{{section|{{{2|}}}}}}}}|{{#invoke:Excerpt|lead|refs={{{references|true}}}}}}}}}</div></div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with extracts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> o8y67nh6owvo4j5pa754hwycil6s0wb 797121 797120 2019-11-13T13:24:37Z en>Sophivorus 0 797121 wikitext text/x-wiki <includeonly><div class="excerpt-block">{{hatnote|1=This section is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span class="mw-editsection-bracket">]</span></span>''|selfref=yes}}<div class="excerpt">{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment|}}}}}|{{#if:{{{section|{{{2|}}}}}}|{{#lsth:{{{article|{{{1}}}}}}|{{{section|{{{2|}}}}}}}}|{{#invoke:Excerpt|lead|refs={{{references|true}}}}}}}}}</div></div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> i09wz7hdalm3u7dam2sxdmyd8y42if1 797122 797121 2019-11-13T13:31:13Z en>Sophivorus 0 797122 wikitext text/x-wiki <includeonly><div class="excerpt-block">{{hatnote|1=This section is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span class="mw-editsection-bracket">]</span></span>''|selfref=yes}}<div class="excerpt"> {{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment|}}}}}|{{#if:{{{section|{{{2|}}}}}}|{{#lsth:{{{article|{{{1}}}}}}|{{{section|{{{2|}}}}}}}}|{{#invoke:Excerpt|lead|refs={{{references|true}}}}}}}}}</div></div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> d7wy2a4xf8rj3t8nxnqg7jzwsbm2n0m 797123 797122 2019-11-13T13:42:30Z en>Sophivorus 0 Add link to history 797123 wikitext text/x-wiki <includeonly><div class="excerpt-block">{{hatnote|1=This section is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span class="mw-editsection-bracket">]</span> <span class="mw-editsection-bracket">[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=history}} history]<span class="mw-editsection-bracket">]</span></span>''|selfref=yes}}<div class="excerpt"> {{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment|}}}}}|{{#if:{{{section|{{{2|}}}}}}|{{#lsth:{{{article|{{{1}}}}}}|{{{section|{{{2|}}}}}}}}|{{#invoke:Excerpt|lead|refs={{{references|true}}}}}}}}}</div></div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> s88uth7qr0c11o0307hx86suy7tbeap 797124 797123 2019-11-13T13:44:38Z en>Sophivorus 0 797124 wikitext text/x-wiki <includeonly><div class="excerpt-block">{{hatnote|1=This section is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span>]</span> <span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=history}} history]<span>]</span></span>''|selfref=yes}}<div class="excerpt"> {{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment|}}}}}|{{#if:{{{section|{{{2|}}}}}}|{{#lsth:{{{article|{{{1}}}}}}|{{{section|{{{2|}}}}}}}}|{{#invoke:Excerpt|lead|refs={{{references|true}}}}}}}}}</div></div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> guhxybu2197zgoyjmopjnan4yutjkbv 797125 797124 2019-11-13T14:39:01Z en>Sophivorus 0 Wrap extract with [[Template:Trim]] to ensure no extra line breaks 797125 wikitext text/x-wiki <includeonly><div class="excerpt-block">{{hatnote|1=This section is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span>]</span> <span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=history}} history]<span>]</span></span>''|selfref=yes}}<div class="excerpt"> {{trim|{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment|}}}}}|{{#if:{{{section|{{{2|}}}}}}|{{#lsth:{{{article|{{{1}}}}}}|{{{section|{{{2|}}}}}}}}|{{#invoke:Excerpt|lead|refs={{{references|true}}}}}}}}}}}</div></div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> gwf8djxrdrdv0ltbmc597wxqvihrc2g 797126 797125 2019-11-13T15:34:40Z en>Sophivorus 0 Transclude main image too 797126 wikitext text/x-wiki <includeonly><div class="excerpt-block">{{hatnote|1=This section is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span>]</span> <span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=history}} history]<span>]</span></span>''|selfref=yes}}<div class="excerpt"> {{trim|{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment|}}}}}|{{#if:{{{section|{{{2|}}}}}}|{{#lsth:{{{article|{{{1}}}}}}|{{{section|{{{2|}}}}}}}}|{{Transclude lead excerpt|1={{{article|{{{1}}}}}}|files=1}}}}}}}}</div></div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> 7rxr8wlly4wbiolgsanz78tn4uradxa 797127 797126 2020-01-07T18:00:03Z en>Sophivorus 0 Replace template for plain invoke for better control 797127 wikitext text/x-wiki <includeonly><div class="excerpt-block">{{hatnote|1=This section is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span>]</span> <span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=history}} history]<span>]</span></span>''|selfref=yes}}<div class="excerpt"> {{trim|{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment|}}}}}|{{#if:{{{section|{{{2|}}}}}}|{{#lsth:{{{article|{{{1}}}}}}|{{{section|{{{2|}}}}}}}}|{{#invoke:Excerpt|lead|1={{{article|{{{1}}}}}}|files=1|keepRefs=1}}}}}}}}</div></div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> f98lx5b6govvnxo0y0ddkgssfp2tppo 797128 797127 2020-01-07T21:54:49Z en>Sophivorus 0 Replace #lsth for [[Module:Excerpt]] 797128 wikitext text/x-wiki <includeonly><div class="excerpt-block">{{hatnote|1=This section is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span>]</span> <span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=history}} history]<span>]</span></span>''|selfref=yes}}<div class="excerpt"> {{trim|{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment|}}}}}|{{#if:{{{section|{{{2|}}}}}}|{{#invoke:Excerpt|lead|1={{{article|{{{1}}}}}}#{{{section|{{{2|}}}}}}|files=1|keepRefs=1}}|{{#invoke:Excerpt|lead|1={{{article|{{{1}}}}}}|files=1|keepRefs=1}}}}}}}}</div></div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> o3dvzceaa2bwzgs1ubsbvhnpcaj3f1r 797129 797128 2020-01-07T21:58:05Z en>Sophivorus 0 Return to #lsth until the module no longer removes tables 797129 wikitext text/x-wiki <includeonly><div class="excerpt-block">{{hatnote|1=This section is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span>]</span> <span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=history}} history]<span>]</span></span>''|selfref=yes}}<div class="excerpt"> {{trim|{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment|}}}}}|{{#if:{{{section|{{{2|}}}}}}|{{#lsth:{{{article|{{{1}}}}}}|{{{section|{{{2|}}}}}}}}|{{#invoke:Excerpt|lead|1={{{article|{{{1}}}}}}|files=1|keepRefs=1}}}}}}}}</div></div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> f98lx5b6govvnxo0y0ddkgssfp2tppo 797130 797129 2020-03-17T19:55:26Z en>Sdkb 0 Adding nohat parameter 797130 wikitext text/x-wiki <includeonly><div class="excerpt-block">{{#ifeq: {{{nohat}}} | y | | {{hatnote|1=This section is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span>]</span> <span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=history}} history]<span>]</span></span>''|selfref=yes}}}}<div class="excerpt"> {{trim|{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment|}}}}}|{{#if:{{{section|{{{2|}}}}}}|{{#lsth:{{{article|{{{1}}}}}}|{{{section|{{{2|}}}}}}}}|{{#invoke:Excerpt|lead|1={{{article|{{{1}}}}}}|files=1|keepRefs=1}}}}}}}}</div></div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> s6m2i2kvgc2w93r5kc5kik463ohq5cp 797131 797130 2020-03-17T22:01:59Z en>Sophivorus 0 Improve nohat param 797131 wikitext text/x-wiki <includeonly><div class="excerpt-block">{{#if:{{{nohat|}}}||{{hatnote|1=This section is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span>]</span> <span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=history}} history]<span>]</span></span>''|selfref=yes}}}}<div class="excerpt"> {{trim|{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment|}}}}}|{{#if:{{{section|{{{2|}}}}}}|{{#lsth:{{{article|{{{1}}}}}}|{{{section|{{{2|}}}}}}}}|{{#invoke:Excerpt|lead|1={{{article|{{{1}}}}}}|files=1|keepRefs=1}}}}}}}}</div></div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> 8kojsdu0y0q9cmh9ijxyz3ln956ayju 797132 797131 2020-03-20T13:42:05Z en>Sophivorus 0 Add option to select what paragraphs to filter, see talk page 797132 wikitext text/x-wiki <includeonly><div class="excerpt-block">{{#if:{{{nohat|}}}||{{hatnote|1=This section is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span>]</span> <span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=history}} history]<span>]</span></span>''|selfref=yes}}}}<div class="excerpt"> {{trim|{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment|}}}}}|{{#if:{{{section|{{{2|}}}}}}|{{#invoke:Excerpt|lead|1={{{article|{{{1}}}}}}|paragraphs={{{paragraphs|}}}|files={{{files|1}}}|keepRefs=1|keepTables=1|1={{{article}}}#{{{section}}}}}|{{#invoke:Excerpt|lead|1={{{article|{{{1}}}}}}|paragraphs={{{paragraphs|}}}|files={{{files|1}}}|keepTables=1|keepRefs=1}}}}}}}}</div></div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> gs139xad1h209xhbrak57rtvndfnfkd 797133 797132 2020-03-20T13:44:01Z en>Sophivorus 0 797133 wikitext text/x-wiki <includeonly><div class="excerpt-block">{{#if:{{{nohat|}}}||{{hatnote|1=This section is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span>]</span> <span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=history}} history]<span>]</span></span>''|selfref=yes}}}}<div class="excerpt"> {{trim|{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment|}}}}}|{{#if:{{{section|{{{2|}}}}}}|{{#invoke:Excerpt|lead|1={{{article|{{{1}}}}}}|paragraphs={{{paragraphs|}}}|files={{{files|1}}}|keepRefs=1|keepTables=1|1={{{article|{{{1}}}}}}#{{{section|{{{2}}}}}}}}|{{#invoke:Excerpt|lead|1={{{article|{{{1}}}}}}|paragraphs={{{paragraphs|}}}|files={{{files|1}}}|keepTables=1|keepRefs=1}}}}}}}}</div></div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> 65cw1oh9o6c7clok89paoy9ilhfgmz0 797134 797133 2020-03-20T16:19:53Z en>SporkBot 0 Clean up [[:Category:Pages using duplicate arguments in template calls|duplicate template arguments]], marking duplicate parameter for repair 797134 wikitext text/x-wiki <includeonly><div class="excerpt-block">{{#if:{{{nohat|}}}||{{hatnote|1=This section is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span>]</span> <span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=history}} history]<span>]</span></span>''|selfref=yes}}}}<div class="excerpt"> {{trim|{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment|}}}}}|{{#if:{{{section|{{{2|}}}}}}|{{#invoke:Excerpt|lead|DUPLICATE-1={{{article|{{{1}}}}}}|paragraphs={{{paragraphs|}}}|files={{{files|1}}}|keepRefs=1|keepTables=1|1={{{article|{{{1}}}}}}#{{{section|{{{2}}}}}}}}|{{#invoke:Excerpt|lead|1={{{article|{{{1}}}}}}|paragraphs={{{paragraphs|}}}|files={{{files|1}}}|keepTables=1|keepRefs=1}}}}}}}}</div></div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> kwv8f68y62p8t6enpkbfsk54taxke60 797135 797134 2020-03-21T11:56:37Z en>Sophivorus 0 Fix duplicate parameter 797135 wikitext text/x-wiki <includeonly><div class="excerpt-block">{{#if:{{{nohat|}}}||{{hatnote|1=This section is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span>]</span> <span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=history}} history]<span>]</span></span>''|selfref=yes}}}}<div class="excerpt"> {{trim|{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment|}}}}}|{{#if:{{{section|{{{2|}}}}}}|{{#invoke:Excerpt|lead|1={{{article|{{{1}}}}}}#{{{section|{{{2}}}}}}|paragraphs={{{paragraphs|}}}|files={{{files|1}}}|keepRefs=1|keepTables=1}}|{{#invoke:Excerpt|lead|1={{{article|{{{1}}}}}}|paragraphs={{{paragraphs|}}}|files={{{files|1}}}|keepTables=1|keepRefs=1}}}}}}}}</div></div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> 22j9imljvq3d042ksjxkchhtl2n1w6c 797136 797135 2020-03-27T02:47:56Z en>Sophivorus 0 Add detection of broken excerpts 797136 wikitext text/x-wiki <includeonly><div class="excerpt-block">{{#if:{{{nohat|}}}||{{hatnote|1=This section is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span>]</span> <span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=history}} history]<span>]</span></span>''|selfref=yes}}}}<div class="excerpt"> {{trim|{{#if:{{{fragment|}}}|{{#if:{{#lst:{{{article|{{{1}}}}}}|{{{fragment}}}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment}}}}}|[[Category:Articles with broken excerpts]]}}|{{#if:{{{section|{{{2|}}}}}}|{{#if:{{#invoke:Excerpt|lead|1={{{article|{{{1}}}}}}#{{{section|{{{2}}}}}}|paragraphs={{{paragraphs|}}}|files={{{files|1}}}|keepTables=1|keepRefs=1}}|{{#invoke:Excerpt|lead|1={{{article|{{{1}}}}}}#{{{section|{{{2}}}}}}|paragraphs={{{paragraphs|}}}|files={{{files|1}}}|keepTables=1|keepRefs=1}}|[[Category:Articles with broken excerpts]]}}|{{#invoke:Excerpt|lead|1={{{article|{{{1}}}}}}|paragraphs={{{paragraphs|}}}|files={{{files|1}}}|keepTables=1|keepRefs=1}}}}}}}}</div></div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> nem1dnhapfcnehopeowcmuofg9er61d 797137 797136 2020-04-01T21:02:40Z en>Sophivorus 0 Update to catch up with the latest version of [[Module:Excerpt]] 797137 wikitext text/x-wiki <includeonly><div class="excerpt-block">{{#if:{{{nohat|}}}||{{hatnote|1=This section is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span>]</span> <span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=history}} history]<span>]</span></span>''|selfref=yes}}}}<div class="excerpt"> {{trim|{{#if:{{{fragment|}}}|{{#if:{{#lst:{{{article|{{{1}}}}}}|{{{fragment}}}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment}}}}}|[[Category:Articles with broken excerpts]]}}|{{#invoke:Excerpt|lead|1={{{article|{{{1}}}}}}#{{{section|{{{2|}}}}}}|paragraphs={{{paragraphs|}}}|files={{{files|1}}}|keepTables=1|keepRefs=1}}}}}}</div></div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> 2y9zws3bu373ul132cyopbeuz4pvbyo 797138 797137 2020-04-03T17:40:32Z en>Sophivorus 0 797138 wikitext text/x-wiki <includeonly><div class="excerpt-block">{{#if:{{{nohat|}}}||{{hatnote|1=This section is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span>]</span> <span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=history}} history]<span>]</span></span>''|selfref=yes}}}}<div class="excerpt"> {{trim|{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment}}}}}|{{#invoke:Excerpt|lead|1={{{article|{{{1}}}}}}#{{{section|{{{2|}}}}}}|paragraphs={{{paragraphs|}}}|files={{{files|1}}}|keepTables=1|keepRefs=1}}}}}}</div></div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> b7sykvvdib82o5yyiciukwwd847u46b 797139 797138 2020-04-04T11:49:38Z en>Xaosflux 0 <templatestyles src="myTemplate/styles.css" /> 797139 wikitext text/x-wiki <templatestyles src="myTemplate/styles.css" /><includeonly><div class="excerpt-block">{{#if:{{{nohat|}}}||{{hatnote|1=This section is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span>]</span> <span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=history}} history]<span>]</span></span>''|selfref=yes}}}}<div class="excerpt"> {{trim|{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment}}}}}|{{#invoke:Excerpt|lead|1={{{article|{{{1}}}}}}#{{{section|{{{2|}}}}}}|paragraphs={{{paragraphs|}}}|files={{{files|1}}}|keepTables=1|keepRefs=1}}}}}}</div></div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> s1k43gedegbzl1cqc9aom9eekk2622h 797140 797139 2020-04-04T11:49:59Z en>Xaosflux 0 ts 797140 wikitext text/x-wiki <templatestyles src="Excerpt/styles.css" /><includeonly><div class="excerpt-block">{{#if:{{{nohat|}}}||{{hatnote|1=This section is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span>]</span> <span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=history}} history]<span>]</span></span>''|selfref=yes}}}}<div class="excerpt"> {{trim|{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment}}}}}|{{#invoke:Excerpt|lead|1={{{article|{{{1}}}}}}#{{{section|{{{2|}}}}}}|paragraphs={{{paragraphs|}}}|files={{{files|1}}}|keepTables=1|keepRefs=1}}}}}}</div></div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> lebpv1e3bkkox69m4acvbd35d9hsrb4 797141 797140 2020-04-04T14:31:24Z en>Sophivorus 0 Add parameter to transclude subsections and remove bold text from lead excerpts 797141 wikitext text/x-wiki <templatestyles src="Excerpt/styles.css" /><includeonly><div class="excerpt-block">{{#if:{{{nohat|}}}||{{hatnote|1=This section is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span>]</span> <span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=history}} history]<span>]</span></span>''|selfref=yes}}}}<div class="excerpt">{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment}}}}}|{{#if:{{{section|{{{2|}}}}}}|{{#if:{{{subsections|}}}|{{#lsth:{{{article|{{{1}}}}}}|{{{section|{{{2}}}}}}}}|{{#invoke:Excerpt|lead|files={{{files|1}}}|paragraphs={{{paragraphs|}}}|keepTables=1|keepRefs=1|1={{{article|{{{1}}}}}}#{{{section|{{{2}}}}}}}}}}|{{#invoke:Excerpt|lead|files={{{files|1}}}|paragraphs={{{paragraphs|}}}|keepTables=1|keepRefs=1|nobold=1|1={{{article|{{{1}}}}}}}}}}}}</div></div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> 1c52hqyh1c4d9v5vmj560052j467avf 797142 797141 2020-04-04T14:47:06Z en>Xaosflux 0 Protected "[[Template:Excerpt]]": [[WP:High-risk templates|Highly visible template]] ([Edit=Require autoconfirmed or confirmed access] (indefinite) [Move=Require autoconfirmed or confirmed access] (indefinite)) 797141 wikitext text/x-wiki <templatestyles src="Excerpt/styles.css" /><includeonly><div class="excerpt-block">{{#if:{{{nohat|}}}||{{hatnote|1=This section is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span>]</span> <span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=history}} history]<span>]</span></span>''|selfref=yes}}}}<div class="excerpt">{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment}}}}}|{{#if:{{{section|{{{2|}}}}}}|{{#if:{{{subsections|}}}|{{#lsth:{{{article|{{{1}}}}}}|{{{section|{{{2}}}}}}}}|{{#invoke:Excerpt|lead|files={{{files|1}}}|paragraphs={{{paragraphs|}}}|keepTables=1|keepRefs=1|1={{{article|{{{1}}}}}}#{{{section|{{{2}}}}}}}}}}|{{#invoke:Excerpt|lead|files={{{files|1}}}|paragraphs={{{paragraphs|}}}|keepTables=1|keepRefs=1|nobold=1|1={{{article|{{{1}}}}}}}}}}}}</div></div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> 1c52hqyh1c4d9v5vmj560052j467avf 797143 797142 2020-04-04T15:17:45Z en>Sophivorus 0 Add optional parameter to indicate where the excerpt starts and finishes 797143 wikitext text/x-wiki <templatestyles src="Excerpt/styles.css" /><includeonly><div class="excerpt-block">{{#if:{{{indicator|}}}|<div style="border-left: 3px solid #c8ccd1; margin: 1em 0; padding-left: 1em;">}}{{#if:{{{nohat|}}}||{{hatnote|1={{#if:{{{indicator|}}}|This|This section}} is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span>]</span> <span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=history}} history]<span>]</span></span>''|selfref=yes}}}}<div class="excerpt">{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment}}}}}|{{#if:{{{section|{{{2|}}}}}}|{{#if:{{{subsections|}}}|{{#lsth:{{{article|{{{1}}}}}}|{{{section|{{{2}}}}}}}}|{{#invoke:Excerpt|lead|files={{{files|1}}}|paragraphs={{{paragraphs|}}}|keepTables=1|keepRefs=1|1={{{article|{{{1}}}}}}#{{{section|{{{2}}}}}}}}}}|{{#invoke:Excerpt|lead|files={{{files|1}}}|paragraphs={{{paragraphs|}}}|keepTables=1|keepRefs=1|nobold=1|1={{{article|{{{1}}}}}}}}}}}}</div>{{#if:{{{marcador|}}}|</div>}}</div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> nsiashh9j8pvfls2g8zefgf63w6gws5 797144 797143 2020-04-04T15:19:17Z en>Sophivorus 0 797144 wikitext text/x-wiki <templatestyles src="Excerpt/styles.css" /><includeonly><div class="excerpt-block">{{#if:{{{indicator|}}}|<div style="border-left: 3px solid #c8ccd1; margin: 1em 0; padding-left: 1em;">}}{{#if:{{{nohat|}}}||{{hatnote|1={{#if:{{{indicator|}}}|This|This section}} is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span>]</span> <span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=history}} history]<span>]</span></span>''|selfref=yes}}}}<div class="excerpt">{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment}}}}}|{{#if:{{{section|{{{2|}}}}}}|{{#if:{{{subsections|}}}|{{#lsth:{{{article|{{{1}}}}}}|{{{section|{{{2}}}}}}}}|{{#invoke:Excerpt|lead|files={{{files|1}}}|paragraphs={{{paragraphs|}}}|keepTables=1|keepRefs=1|1={{{article|{{{1}}}}}}#{{{section|{{{2}}}}}}}}}}|{{#invoke:Excerpt|lead|files={{{files|1}}}|paragraphs={{{paragraphs|}}}|keepTables=1|keepRefs=1|nobold=1|1={{{article|{{{1}}}}}}}}}}}}</div>{{#if:{{{indicator|}}}|</div>}}</div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> 5hij8cwi8acx1nfld7gymltqlwua4ce 797145 797144 2020-04-04T16:59:16Z en>Izno 0 swap to use class in the indicator also 797145 wikitext text/x-wiki <templatestyles src="Excerpt/styles.css" /><includeonly><div class="excerpt-block">{{#if:{{{indicator|}}}|<div class="excerpt-indicator">}}{{#if:{{{nohat|}}}||{{hatnote|1={{#if:{{{indicator|}}}|This|This section}} is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span>]</span> <span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=history}} history]<span>]</span></span>''|selfref=yes}}}}<div class="excerpt">{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment}}}}}|{{#if:{{{section|{{{2|}}}}}}|{{#if:{{{subsections|}}}|{{#lsth:{{{article|{{{1}}}}}}|{{{section|{{{2}}}}}}}}|{{#invoke:Excerpt|lead|files={{{files|1}}}|paragraphs={{{paragraphs|}}}|keepTables=1|keepRefs=1|1={{{article|{{{1}}}}}}#{{{section|{{{2}}}}}}}}}}|{{#invoke:Excerpt|lead|files={{{files|1}}}|paragraphs={{{paragraphs|}}}|keepTables=1|keepRefs=1|nobold=1|1={{{article|{{{1}}}}}}}}}}}}</div>{{#if:{{{indicator|}}}|</div>}}</div>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> obiggbnfbwxexu2p8cq358236yrw7no 797146 797145 2020-04-07T00:41:03Z en>Sdkb 0 Adding parameter to allow use of "span" rather than "div" to avoid unwanted line breaks 797146 wikitext text/x-wiki <templatestyles src="Excerpt/styles.css" /><includeonly><{{{tag|div}}} class="excerpt-block">{{#if:{{{indicator|}}}|<{{{tag|div}}} class="excerpt-indicator">}}{{#if:{{{nohat|}}}||{{hatnote|1={{#if:{{{indicator|}}}|This|This section}} is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span>]</span> <span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=history}} history]<span>]</span></span>''|selfref=yes}}}}<{{{tag|div}}} class="excerpt">{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment}}}}}|{{#if:{{{section|{{{2|}}}}}}|{{#if:{{{subsections|}}}|{{#lsth:{{{article|{{{1}}}}}}|{{{section|{{{2}}}}}}}}|{{#invoke:Excerpt|lead|files={{{files|1}}}|paragraphs={{{paragraphs|}}}|keepTables=1|keepRefs=1|1={{{article|{{{1}}}}}}#{{{section|{{{2}}}}}}}}}}|{{#invoke:Excerpt|lead|files={{{files|1}}}|paragraphs={{{paragraphs|}}}|keepTables=1|keepRefs=1|nobold=1|1={{{article|{{{1}}}}}}}}}}}}</{{{tag|div}}}>{{#if:{{{indicator|}}}|</{{{tag|div}}}>}}</{{{tag|div}}}>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> ajrqz9xr9pole1bedd8atiyjxhaa8wl 797147 797146 2020-04-08T01:56:24Z en>Sophivorus 0 797147 wikitext text/x-wiki <templatestyles src="Excerpt/styles.css" /><includeonly><{{{tag|div}}} class="excerpt-block">{{#if:{{{indicator|}}}|<{{{tag|div}}} class="excerpt-indicator">}}{{#if:{{{nohat|}}}||{{hatnote|1={{#if:{{{indicator|}}}|This|This section}} is an [[Wikipedia:Excerpts|excerpt]] from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span>]</span> <span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=history}} history]<span>]</span></span>''|selfref=yes}}}}<{{{tag|div}}} class="excerpt">{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment}}}}}|{{#invoke:Excerpt|lead|nobold=1|files={{{files|1}}}|paragraphs={{{paragraphs|}}}|keepTables={{{tables|1}}}|keepRefs={{{references|1}}}|keepSubsections={{{subsections|}}}|1={{{article|{{{1}}}}}}#{{{section|{{{2|}}}}}}}}}}</{{{tag|div}}}>{{#if:{{{indicator|}}}|</{{{tag|div}}}>}}</{{{tag|div}}}>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> tar8s1m593lzro6tk1xrmj7ldoqntm9 797148 797147 2020-04-21T21:14:54Z en>Sdkb 0 /* top */ Removing link to [[WP:Excerpts]] (see talk) 797148 wikitext text/x-wiki <templatestyles src="Excerpt/styles.css" /><includeonly><{{{tag|div}}} class="excerpt-block">{{#if:{{{indicator|}}}|<{{{tag|div}}} class="excerpt-indicator">}}{{#if:{{{nohat|}}}||{{hatnote|1={{#if:{{{indicator|}}}|This|This section}} is an excerpt from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span>]</span> <span>[</span>[{{fullurl:{{{1|{{{article}}}}}}|action=history}} history]<span>]</span></span>''|selfref=yes}}}}<{{{tag|div}}} class="excerpt">{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment}}}}}|{{#invoke:Excerpt|lead|nobold=1|files={{{files|1}}}|paragraphs={{{paragraphs|}}}|keepTables={{{tables|1}}}|keepRefs={{{references|1}}}|keepSubsections={{{subsections|}}}|1={{{article|{{{1}}}}}}#{{{section|{{{2|}}}}}}}}}}</{{{tag|div}}}>{{#if:{{{indicator|}}}|</{{{tag|div}}}>}}</{{{tag|div}}}>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> ey342liny37qts35997rm71n99h5lnh 797149 797148 2020-04-21T21:42:51Z en>Sdkb 0 Fixing formatting to add space (same as is used for normal section edit links), and removing the history link, per [[WP:READER]] 797149 wikitext text/x-wiki <templatestyles src="Excerpt/styles.css" /><includeonly><{{{tag|div}}} class="excerpt-block">{{#if:{{{indicator|}}}|<{{{tag|div}}} class="excerpt-indicator">}}{{#if:{{{nohat|}}}||{{hatnote|1={{#if:{{{indicator|}}}|This|This section}} is an excerpt from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span>[ </span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span> ]</span></span>''|selfref=yes}}}}<{{{tag|div}}} class="excerpt">{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment}}}}}|{{#invoke:Excerpt|lead|nobold=1|files={{{files|1}}}|paragraphs={{{paragraphs|}}}|keepTables={{{tables|1}}}|keepRefs={{{references|1}}}|keepSubsections={{{subsections|}}}|1={{{article|{{{1}}}}}}#{{{section|{{{2|}}}}}}}}}}</{{{tag|div}}}>{{#if:{{{indicator|}}}|</{{{tag|div}}}>}}</{{{tag|div}}}>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> sublxbzmlcoodh76uksxlcs3rua56ze 797150 797149 2020-04-22T01:15:03Z en>Oshwah 0 Changed protection level for "[[Template:Excerpt]]": [[WP:High-risk templates|Highly visible template]] ([Edit=Require template editor access] (indefinite) [Move=Require template editor access] (indefinite)) 797149 wikitext text/x-wiki <templatestyles src="Excerpt/styles.css" /><includeonly><{{{tag|div}}} class="excerpt-block">{{#if:{{{indicator|}}}|<{{{tag|div}}} class="excerpt-indicator">}}{{#if:{{{nohat|}}}||{{hatnote|1={{#if:{{{indicator|}}}|This|This section}} is an excerpt from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span>[ </span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span> ]</span></span>''|selfref=yes}}}}<{{{tag|div}}} class="excerpt">{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment}}}}}|{{#invoke:Excerpt|lead|nobold=1|files={{{files|1}}}|paragraphs={{{paragraphs|}}}|keepTables={{{tables|1}}}|keepRefs={{{references|1}}}|keepSubsections={{{subsections|}}}|1={{{article|{{{1}}}}}}#{{{section|{{{2|}}}}}}}}}}</{{{tag|div}}}>{{#if:{{{indicator|}}}|</{{{tag|div}}}>}}</{{{tag|div}}}>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> sublxbzmlcoodh76uksxlcs3rua56ze 797151 797150 2020-04-22T01:15:03Z en>Oshwah 0 Adding {{pp-template}} ([[WP:TW|TW]]) 797151 wikitext text/x-wiki <noinclude>{{pp-template|small=yes}}</noinclude><templatestyles src="Excerpt/styles.css" /><includeonly><{{{tag|div}}} class="excerpt-block">{{#if:{{{indicator|}}}|<{{{tag|div}}} class="excerpt-indicator">}}{{#if:{{{nohat|}}}||{{hatnote|1={{#if:{{{indicator|}}}|This|This section}} is an excerpt from [[{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|{{urlencode:#{{{section|{{{2}}}}}}|PATH}}}}|{{{article|{{{1}}}}}}{{#if:{{{section|{{{2|}}}}}}|<nowiki> § </nowiki>{{{section|{{{2}}}}}}}}]]''<span class="mw-editsection-like plainlinks"><span>[ </span>[{{fullurl:{{{1|{{{article}}}}}}|action=edit}} edit]<span> ]</span></span>''|selfref=yes}}}}<{{{tag|div}}} class="excerpt">{{#if:{{{fragment|}}}|{{#lst:{{{article|{{{1}}}}}}|{{{fragment}}}}}|{{#invoke:Excerpt|lead|nobold=1|files={{{files|1}}}|paragraphs={{{paragraphs|}}}|keepTables={{{tables|1}}}|keepRefs={{{references|1}}}|keepSubsections={{{subsections|}}}|1={{{article|{{{1}}}}}}#{{{section|{{{2|}}}}}}}}}}</{{{tag|div}}}>{{#if:{{{indicator|}}}|</{{{tag|div}}}>}}</{{{tag|div}}}>{{#switch:{{NAMESPACENUMBER}}|0=[[Category:Articles with excerpts]]}}</includeonly><noinclude>{{Documentation}}</noinclude> ra312p93bo2zt8v1cyuxfgzvhuub3iu 797152 797151 2020-04-23T18:11:15Z en>Ahecht 0 Switch to lua 797152 wikitext text/x-wiki <noinclude>{{pp-template|small=yes}}</noinclude>{{#invoke:Excerpt/sandbox|excerpt}}<noinclude> {{Documentation}} </noinclude> gcceloaj1j37k55baeat68em80qkeid 797153 797152 2020-04-23T18:11:48Z en>Ahecht 0 <includeonly> 797153 wikitext text/x-wiki <noinclude>{{pp-template|small=yes}}</noinclude><includeonly>{{#invoke:Excerpt/sandbox|excerpt}}</includeonly><noinclude> {{Documentation}} </noinclude> fincy7q6dcbi3ehygtszpwh6zzdwec6 797154 797153 2020-04-23T19:56:31Z en>Ahecht 0 rm /sandbox 797154 wikitext text/x-wiki <noinclude>{{pp-template|small=yes}}</noinclude><includeonly>{{#invoke:Excerpt|excerpt}}</includeonly><noinclude> {{Documentation}} </noinclude> q73h25ptjta5eyy8d20nd72srxk22or 797155 797154 2020-05-13T13:16:51Z en>Sophivorus 0 Update from latest sandbox version 797155 wikitext text/x-wiki <noinclude>{{pp-template|small=yes}}</noinclude><includeonly>{{#invoke:Excerpt/templates|excerpt}}</includeonly><noinclude> {{Documentation}} </noinclude> cb8nynxkpk8n1ft0p3v26gimj8f2ejt 797156 797155 2020-05-13T13:21:58Z en>Pppery 0 Please stop misusing your global interface editor access to edit template-protected templates 797156 wikitext text/x-wiki <noinclude>{{pp-template|small=yes}}</noinclude><includeonly>{{#invoke:Excerpt|excerpt}}</includeonly><noinclude> {{Documentation}} </noinclude> q73h25ptjta5eyy8d20nd72srxk22or 797157 797156 2020-06-01T12:50:37Z en>Sophivorus 0 Update to new version 797157 wikitext text/x-wiki <includeonly>{{#invoke:Excerpt/templates|excerpt}}</includeonly><noinclude>{{pp-template|small=yes}}{{Documentation}}</noinclude> goy0ll0n9utyhprzqj51maqkq4pw09d 797158 797157 2020-08-28T12:35:39Z en>Sophivorus 0 Update to latest, see [[Module talk:Excerpt#New version]]. Going through necessary but temporary /staging submodule 797158 wikitext text/x-wiki <includeonly>{{#invoke:Excerpt/staging|main | class = {{#if:{{{indicator|}}}|excerpt-indicator|{{{class|}}}}} | hat = {{#if:{{{nohat|}}}|0|{{{hat|}}}}}<!-- Backwards compatibility --> | inline = {{#ifeq:{{{tag|}}}|span|1|{{{inline|}}}}}<!-- Backwards compatibility --> | sections = {{{subsections|{{{sections|}}}}}}<!-- Backwards compatibility --> }}</includeonly><noinclude>{{pp-template|small=yes}}{{Documentation}}</noinclude> 5j6p4dzhz9tarirmu8hlzfjr713ijm2 797159 797158 2020-08-29T15:07:53Z en>Sophivorus 0 Move from staging to production version 797159 wikitext text/x-wiki <includeonly>{{#invoke:Excerpt|main | class = {{#if:{{{indicator|}}}|excerpt-indicator|{{{class|}}}}} | hat = {{#if:{{{nohat|}}}|0|{{{hat|}}}}}<!-- Backwards compatibility --> | inline = {{#ifeq:{{{tag|}}}|span|1|{{{inline|}}}}}<!-- Backwards compatibility --> | sections = {{{subsections|{{{sections|}}}}}}<!-- Backwards compatibility --> }}</includeonly><noinclude>{{pp-template|small=yes}}{{Documentation}}</noinclude> fiwojhawj717ow8w57uene31wad18re 797160 797159 2022-01-20T20:09:16Z en>Sophivorus 0 After replacing all deprecated parameters in all articles using them, I'm removing their backwards compatibility to simplify both code and documentation 797160 wikitext text/x-wiki <includeonly>{{#invoke:Excerpt|main}}</includeonly><noinclude>{{pp-template|small=yes}}{{Documentation}}</noinclude> rp8yr72p8ph9ror00wcnysz9vezq15t 797161 797160 2022-01-21T20:09:52Z en>Sophivorus 0 Add 'section', 'fragment', 'page' and 'article' as aliases because the new version of the module doesn't support aliases 797161 wikitext text/x-wiki <includeonly>{{#invoke:Excerpt|main | 1 = {{{article|{{{page|{{{1}}}}}}}}} | 2 = {{{section|{{{fragment|{{{2|}}}}}}}}} }}</includeonly><noinclude>{{pp-template|small=yes}}{{Documentation}}</noinclude> 1i0qym567lcavwa7ckkeuai2og4fwsi 797162 797161 2022-07-08T12:04:35Z en>Paine Ellsworth 0 not needed - provided by Documentation template 797162 wikitext text/x-wiki <includeonly>{{#invoke:Excerpt|main | 1 = {{{article|{{{page|{{{1}}}}}}}}} | 2 = {{{section|{{{fragment|{{{2|}}}}}}}}} }}</includeonly><noinclude>{{Documentation}}</noinclude> i0godpcyj2i0s4wm83my8ehm3bnfv5n 797163 797162 2026-06-08T20:19:11Z SM7 3953 49 revisions imported from [[:en:Template:Excerpt]] 797162 wikitext text/x-wiki <includeonly>{{#invoke:Excerpt|main | 1 = {{{article|{{{page|{{{1}}}}}}}}} | 2 = {{{section|{{{fragment|{{{2|}}}}}}}}} }}</includeonly><noinclude>{{Documentation}}</noinclude> i0godpcyj2i0s4wm83my8ehm3bnfv5n टेम्पलेट:Excerpt/doc 10 101018 797164 2026-06-02T19:04:17Z en>Myselfthethird 0 Closing parenthesis 797164 wikitext text/x-wiki {{High-use}} {{Documentation subpage}} {{never subst}} {{Lua|Module:Excerpt}} This template is used for reusing parts of pages in other pages. This practice has various [[#Advantages and disadvantages|advantages and disadvantages]]. This template extends the capabilities of the built-in [[Help:Transclusion|normal transclusion]] and [[Help:Labeled section transclusion|labeled section transclusion]]. == Usage == * <code><nowiki>{{Excerpt|Page title}}</nowiki></code> – Transclude the lead section ([[Africa#Architecture|example]]) * <code><nowiki>{{Excerpt|Page title|Section title}}</nowiki></code> – Transclude a specific section, excluding any subsections ([[Eating#Mammals|example]]) == Parameters == There is one required parameter, and numerous optional ones for configuring the excerpt: === Summary === '''Source identification''' * {{para|1}} – Name of the article or page to transclude. '''Required.''' Aliases: {{para|article}} or {{para|page}}. * {{para|2}} – Name of the section or tag to transclude. Optional; if omitted, transcludes the lead section (content above the first section header). Aliases: {{para|section}} or {{para|fragment}}. '''Transclusion config''' Transcludable content is defined as one of several ''element types'': {{pval|file}}, {{pval|list}}, {{pval|paragraph}}, {{pval|reference}}, {{pval|subsection}}, {{pval|table}}, or {{pval|template}}. Config parameters specify which ''element type'' to transclude, and in some cases, ''how many'' and ''which'' items of that type to transclude. All config parameters are optional; if omitted, all items of all element types are transcluded from the source page identified by the two unnamed parameters. Some element types support conditional item transclusion by specifying an item number range (1-3) or comma series (1, 2, 5); these types include: files, lists, paragraphs, and tables. There are ten optional transclusion configuration parameters: * {{para|only}} – Element types to transclude. Values: {{pval|file(s)}}, {{pval|list(s)}}, {{pval|table(s)}}, {{pval|template(s)}}, {{pval|paragraph(s)}}. Default: all element types. * {{para|files}} – [[WP:FILE|Files]] to transclude. Default: all files. Same basic syntax as {{para|paragraphs}}, but see {{slink||Details}}. ** {{para|onlyfreefiles|no}} – Enables transclusion of [[WP:Non-free content|non-free files]]. Default: exclude non-free content. * {{para|lists}} – Lists ([[MOS:LIST#Bulleted lists|bulleted]], [[MOS:LIST#Numbered lists|numbered]]) to transclude. Default: all lists. Same syntax as for {{para|paragraphs}}. * {{para|paragraphs}} – [[H:PARAGRAPH|Paragraphs]] to transclude. Default: all paragraphs. * {{para|references|no}} – Exclude all [[WP:REF|references]] between <code><nowiki><ref>...</ref></nowiki></code> tags. * {{para|subsections|yes}} – Include [[MOS:SECTIONS|subsections]] of the transcluded section. Default: only content above the first subsection header. * {{para|tables}} – [[Help:Table|Tables]] to transclude. Default: all tables. Same basic syntax as {{para|paragraphs}}, but see {{slink||Details}}. * {{para|templates}} – [[Help:Template|Templates]] to transclude. By default all templates are transcluded, except those blacklisted at [[Module:Excerpt/config]]. See {{slink||Details}} for how to specify a specific template or templates for inclusion or exclusion. '''Style and extra features''' These optional parameters alter the way transcluded items are displayed: * {{para|bold|yes}} – Preserve '''bold''' text. * {{para|briefdates|yes}} – Abbreviate birth and death information to (YYYY-YYYY) format * {{para|displaytitle}} – Change the text of the link in the hatnote. For example, [[WP:ITHAT|add italics]], subscripts, etc. * {{para|hat|no}} – Hide the [[Wikipedia:Hatnote|hatnote]] "This section is an excerpt from..." * {{para|inline|yes}} – Remove the <code><nowiki><div></nowiki></code> tags around the excerpt, to use it inside other text, or to add references or other content after it [[#Suppress line breaks between paragraphs|with no paragraph break]] between them. Also hides the hatnote; to reestablish it, use {{tl|transclusion notice}} above the Excerpt invocation. * {{para|links|no}} – Unlink all [[H:WIKILINK|wikilinks]] and render as plain text. * {{para|quote|yes}} – Wrap the excerpt with <code><nowiki><blockquote></nowiki></code> tags. * {{para|this}} – Change the initial text of the hatnote. For example, if the transcluded content is a gallery, you can set {{para|this|This gallery is}} so that the hatnote reads "This gallery is an excerpt from..." === Details === * {{para|1}} or {{para|article}} or {{para|page}} *: By default the lead section is transcluded ([[Africa#Water|example]]). If the page contains an infobox, the image and caption of the infobox will be transcluded (unless {{para|files|0}} is set). Also, templates listed at [[Module:Excerpt/config]] will not be transcluded (unless requested explicitly with {{para|templates}}, see below). * {{para|2}} or {{para|section}} or {{para|fragment}} *: Name of the section to transclude ([[Eating#Mammals|example]]) or of the <code><nowiki><section></nowiki></code> tag to transclude ([[Axiom Space#Ax-1|example]]). In the case of a section tag, the section must be marked with <code><nowiki><section begin="Name of the fragment" /></nowiki></code> and <code><nowiki><section end="Name of the fragment" /></nowiki></code> in the page to be transcluded. Notice that this template provides other ways of targeting specific fragments of a page without having to resort to section tags. * {{para|only}} *: The ''element type'' to transclude, excluding all other types. By default all element types are transcluded. Param {{para|only}} is an exclusionary param, and excludes all other types of elements, except the one you name, so that for example, specifying {{para|only|paragraphs}} excludes all lists, tables, templates, and so on. Param values can be in the singular (e.g., {{para|only|paragraph}}) or plural (e.g., {{para|only|paragraphs}}) and mean different things: in the singular, only the first item of that element type is transcluded; in the plural, all items are. *:* {{para|only|file}} – Transclude only the first file (but no lists, paragraphs, tables, etc.) *:* {{para|only|files}} – Transclude all files (but nothing else) *:* {{para|only|filename}} – Transclude only the file name (Example.jpg) of the first file (and nothing else). If no file is found, "Noimage.png" will be returned. *:* {{para|only|list}} – Transclude only the first list, exclude all other element types *:* {{para|only|lists}} – Transclude all lists (but nothing else) *:* {{para|only|table}} – Transclude only the first table, exclude all other element types. Does not transclude table templates, use {{para|templates}} for that. *:* {{para|only|tables}} – Transclude all tables (but nothing else) ([[JKT48#Singles|example]]) *:* {{para|only|template}} – Transclude only the first template (excluding templates blacklisted at [[Module:Excerpt/config]], as well as all other element types) *:* {{para|only|templates}} – Transclude all templates (excluding blacklisted templates) (but nothing else) *:* {{para|only|paragraph}} – Transclude only the first paragraph, exclude all other element types *:* {{para|only|paragraphs}} – Transclude all paragraphs (but nothing else) * {{para|files}} *: An ''element item'' param, defining which files, such as images and other media, to transclude. Default: all files. *:* {{para|files|0}} – Transclude no files *:* {{para|files|A.jpg}} – Transclude the file named 'A.jpg' *:* {{para|files|A.jpg, B.png, C.gif}} – Transclude the files named 'A.jpg', 'B.png' and 'C.gif' *:* {{para|files|.+%.png}} – Transclude all PNG files *:* {{para|files|-A.jpg}} – Transclude all files except the one named 'A.jpg' *:* {{para|files|-A.jpg, B.png, C.gif}} – Transclude all files except the ones named 'A.jpg', 'B.png' and 'C.gif' *:* {{para|files|-.+%.png}} – Transclude all non-PNG files * {{para|paragraphs}} *: An ''element item'' param, defining which paragraphs to transclude. By default all paragraphs are transcluded. *:* {{para|paragraphs|0}} – Transclude no paragraphs *:* {{para|paragraphs|1}} – Transclude the first paragraph *:* {{para|paragraphs|2}} – Transclude the second paragraph *:* {{para|paragraphs|1,3}} – Transclude the first and third paragraphs *:* {{para|paragraphs|1-3}} – Transclude the first, second and third paragraphs *:* {{para|paragraphs|1-3,5}} – Transclude the first, second, third and fifth paragraphs *:* {{para|paragraphs|-1}} – Transclude all paragraphs except the first *:* {{para|paragraphs|-2}} – Transclude all paragraphs except the second *:* {{para|paragraphs|-1,3}} – Transclude all paragraphs except the first and third *:* {{para|paragraphs|-1-3}} – Transclude all paragraphs except the first, second and third *:* {{para|paragraphs|-1-3,5}} – Transclude all paragraphs except the first, second, third and fifth * {{para|subsections|yes}} *: An ''element item'' param, defining which subsections of the source to transclude. Default: only the portion of the source lying above the first subsection header. Notice that if the transclusion is done from a section level 3 in the transcluding page, and the transcluded subsections are also level 3, then transcluded subsections will show with the same hierarchy as the transcluding section, which may not be the desired outcome, so use with caution. * {{para|tables}} *: An ''element item'' param, defining which tables to transclude. Default: all tables. Does not transclude table templates just above the tables. Same syntax as when transcluding paragraphs, but also: *:* {{para|tables|Stats2020}} – Transclude the table with id 'Stats2020' *:* {{para|tables|Stats2020, Stats2019, Stats2018}} – Transclude the tables with ids 'Stats2020', 'Stats2019' and 'Stats2018' *:* {{para|tables|-Stats2020}} – Transclude all tables except the one with id 'Stats2020' *:* {{para|tables|-Stats2020, Stats2019, Stats2018}} – Transclude all tables except the ones with ids 'Stats2020', 'Stats2019' and 'Stats2018' * {{para|templates}} *: An ''element item'' param, defining which templates to transclude. Default: all templates except those blacklisted at [[Module:Excerpt/config]]. Using a hyphen (minus sign) before a comma-delimited list of templates excludes those templates from transclusion. *:* {{para|templates|-Ocean}} – Add the template 'Ocean' to the blacklist *:* {{para|templates|-Ocean, Nature}} – Add the templates 'Ocean' and 'Nature' to the blacklist *:* {{para|templates|Infobox person}} – Ignore the blacklist and transclude the template 'Infobox person' *:* {{para|templates|Infobox person, Ocean}} – Ignore the blacklist and transclude the templates 'Infobox person' and 'Ocean' *:* {{para|templates|.*}} – Ignore the blacklist and transclude all templates == Tips and how-to == === Compared to #section === {{Further|Help:Transclusion#Standard section transclusion}} For simple cases of transcluding sections of articles, the {{pf|section}}, {{pf|section-x}}, and {{pf|section-h}} (abbreviated {{pf|lst}}, {{pf|lstx}}, and {{pf|lsth}})) parser functions can be used instead of this template. {{pf|lsth|''article''|''fragmentname''}} will transclude the section of "''article''" with the header "''fragmentname''", and {{pf|lsth|''article''}} will transclude the lead section of "''article''". Excerpting only specific paragraphs can be done by marking up the source article with <code><nowiki><section begin=</nowiki>''<nowiki>fragmentname</nowiki>''<nowiki>/>...<section end=</nowiki>''<nowiki>fragmentname</nowiki>''<nowiki>/></nowiki></code> tags and using {{pf|lst|''article''|''fragmentname''}} to transclude those fragments, which is equivalent to using the {{para|fragment|''fragmentname''}} parameter with this template. {{pf|lsth|''article''|''fragmentname''}} can also be used to transclude everything ''but'' those fragments. The text will not be trimmed of excess whitespace, there will not be a header (equivalent to {{para|hat|no}}), and all files, templates, tables, references, and subsections will be included unless the source article is marked up with <code><nowiki><section begin=</nowiki>''<nowiki>fragmentname</nowiki>''<nowiki>/>...<section end=</nowiki>''<nowiki>fragmentname</nowiki>''<nowiki>/></nowiki></code>, {{tag|noinclude}}, or {{tag|onlyinclude}} tags. [[Help:Self link|Self links]] will appear in bold. === Citations === ==== Differing styles ==== {{Further|Wikipedia:Citing sources#Variation in citation methods}} It can happen that the source you want to excerpt contains footnotes in a [[WP:CITEVAR|different citation style]] than your article, and excerpting the source would cause a citation style mismatch, which is contrary to the guideline on [[Wikipedia:Citing sources|citing sources]]. Sometimes, excerpt can still be used, while avoiding a mismatch in style, by the use of params {{para|references|no}} and {{para|inline|yes}}. If the source you want to excerpt has multiple ref-tags interspersed throughout the source, and they need to display exactly in those locations in order to maintain full [[WP:V|verifiability]], then this source might not be a good candidate for transclusion via the {{tl|excerpt}} template, and [[WP:CWW|copying the content]] from the source into the article might be a better choice. However, you can still use the Excerpt template, if the source page you want to excerpt meets '''either''' of these conditions: * references are found mostly or all at the end of the text to be excerpted; or * references are scattered throughout, but could legitimately be regrouped at the end of the excerpt without adversely affecting [[WP:V|verifiability]]. In either case, use params {{para|references|no}} to strip the ref-tags from the transcluded content, and {{para|inline|yes}} to define the excerpt as an [[HTML element#Inline elements|inline display element]] in order to {{slink||Suppress line breaks between paragraphs}}, and then manually append a copy of all the citations in the source immediately after the excerpt tag ending curly braces in the target article, with no intervening line breaks, white space, or other characters between the tag and the appended references. The copied references will have to be manually converted from [[Wikipedia:Citing sources#Short and full citations|short footnote-style to full, inline citation-style]], or vice-versa, to match the citation style of the target. '''Note:''' citations are not creative content, and [[Wikipedia:Copying within Wikipedia#Where attribution is not needed|attribution is not needed]] for copying them. ==== Another approach ==== It is advised to seek consensus before attempting this layout change. If there are few shortened footnotes, a new subsection can be added to the "References" section to allow {{tl|sfn}} to link to the correct citations. Here is one approach, where the {{tl|sfn}} citations list in the excerpted article "SCBA" is titled "Works cited": <syntaxhighlight lang="wikitext" highlight="7-8"> == Transcluded section == {{excerpt|SCBA}} == References == {{reflist}} === From SCBA === {{excerpt|SCBA|Works cited|hat=0}} </syntaxhighlight> {{tl|sfn}} footnotes will now link to the citations created in the excerpted subsection. ==== Ref name collision ==== To prevent the possibility of collisions between [[WP:NAMEDREFS|named references]] in the transcluding and excerpted pages which would otherwise generate a [[Help:Cite errors/Cite error references duplicate key|duplicate key error]], all ref names in the excerpted content are altered by prefixing the name with the pagename of the excerpted page. Thus, if the ref on page {{kbd|MyArticle}} looks like <code><nowiki><ref name="Jones-2024"></nowiki></code>, then when excerpted by {{kbd|TargetPage}} it will be changed to <code><nowiki><ref name="MyArticle Jones-2024"></nowiki></code>, in order to avoid a collision with a possible "Jones-2024" ref already on the target page. === Excerpt trees === [[File:Excerpt tree.png|class=skin-invert-image|thumb|Visual representation of an imaginary excerpt tree.]] When a very general article uses excerpts from more specific articles, which in turn use excerpts from even more specific articles, then a [[tree structure]] emerges, called an "Excerpt tree". Here you can navigate the main excerpt trees on the English Wikipedia. It's useful for editors interested in expanding or improving them. To navigate the trees, click the following button<sup><abbr title="{{Int:gadgets-sister}}">(S)</abbr></sup>: {{Clickable button|url={{fullurl:{{FULLPAGENAME}}#Excerpt trees|withJS=MediaWiki:ExcerptTree.js}} |2=See the excerpt trees}} <div class="ExcerptTree" style="display: none;"> * [[January 6 United States Capitol attack]] * [[Algae]] * [[Africa]] * [[Aquatic ecosystem]] * [[Bangladesh]] * [[Campanology]] * [[Climate change in Africa]] * [[Climate change in South Asia]] * [[Climate justice]] * [[COVID-19 pandemic]] * [[Ecosystem]] ** [[Aquatic ecosystem]] * [[Effects of climate change on humans]] * [[Environmental impact of agriculture]] * [[Eutrophication]] * [[Fishing industry]] * [[Food]] * [[God]] ** [[Existence of God]] * [[Hygiene]] * [[List of carillons]] * [[List of LGBT-related cases in the United States Supreme Court]] * [[Marine debris]] * [[Middle East]] * [[Ocean]] * [[Plastic pollution]] * [[Public health]] * [[Sanitation]] * [[Sewage]] * [[Sustainable Development Goals]] * [[Tourism]] * [[Wastewater treatment]] * [[Water resources]] </div> === Refinement using inclusion control === {{Further|Help:Template#Noinclude, includeonly, and onlyinclude}} Sometimes, a passage will almost fit for a transclusion, but not quite. In these cases, you can edit the source page to add <code><nowiki><noinclude>...</noinclude></nowiki></code> tags around content you don't want in the excerpt and <code><nowiki><includeonly>...</includeonly></nowiki></code> tags around content you want only in the excerpt. For instance, the page [[COVID-19 misinformation]] begins with "The [[COVID-19 pandemic]] has resulted in [[misinformation]]...". However, when excerpting this lead to the misinformation section of [[COVID-19 pandemic]], we don't need to specify which pandemic we're referring to. Therefore, the code <code><nowiki>The <noinclude>[[COVID-19 pandemic]]</noinclude><includeonly>pandemic</includeonly> has resulted in [[misinformation]]</nowiki></code> can be used at the misinformation page, so that it will appear at the pandemic page as "The pandemic has resulted in [[misinformation]]...". For pages with a high volume of edits, it may be a good idea to leave a hidden comment explaining why the tags are there, so that no one will be [[WP:FENCE|tempted]] to remove them, like so: <code><nowiki>The <noinclude>[[COVID-19 pandemic]]</noinclude><!--These tags are used to refine the excerpt at [[COVID-19 pandemic]]--><includeonly>pandemic</includeonly> has resulted in [[misinformation]]</nowiki></code> Please note that when the <code><nowiki></noinclude></nowiki></code> tag is wrapped into a new line, a character next to it would be interpreted as a line beginning. This can lead to some formatting problems. For example, when a <code><nowiki></noinclude></nowiki></code> at line beginning is succeeded by a [[whitespace character]], the page engine would translate this as a [[Help:Wikitext#Limiting formatting / escaping wiki markup|leading space]] that renders the subsequent paragraph in [[code block]] and [[monospaced font]] with preserved formatting. For this reason, no spaces should separate the <code><nowiki></noinclude></nowiki></code> tag from the text it precedes. === Replacing summary section with excerpt of child article === [[File:How to excerpt.webm|thumb|How to replace a section with an excerpt.]] A section is often a [[WP:Summary style|summary]] in a [[WP:G#Parent article|parent article]] of a more detailed page about a subtopic located in a [[WP:G#child page|child page]]; these are generally linked with [[Template:Main]] in the parent. Sometimes it's convenient to replace the content of such a summary section in the parent with an excerpt of the child page lead (after merging any valuable content of the section into the child page). In such cases, an efficient way to proceed is: # Open the parent section for editing in one tab, and the child article in another. # Copy the text of the parent section and append it to the lead section of the child page. # Consolidate and adjust the combined lead using common sense. # Save the changes in the child article with an [[WP:ES|edit summary]] like: "{{xt|<nowiki>Copied content from [[Page]]. See that article's history for attribution."</nowiki>}} # Back in the parent page section, delete all content except the section header and replace it with an excerpt of the child page. # Save the changes in the section; proposed edit summary: "{{xt|Moved section content to <nowiki>[[Child page title]]</nowiki> and replaced with excerpt.}}" '''IMPORTANT!''' In step four, include the '''full''' edit summary as shown to comply with Wikipedia's [[Wikipedia:Copying within Wikipedia|copyright policy]]. If you forget to do this at the time of the original edit, follow the instructions on [[Wikipedia:Copying within Wikipedia#Repairing insufficient attribution|Repairing insufficient attribution]] to create a dummy edit with the required edit summary. === Suppress line breaks between paragraphs === If you want to merge two excerpted paragraphs from a source into one longer one in your article, use two excerpts instead of one, and change the display mode to [[HTML element#Inline elements|inline]]. So, for example, instead of this : : {{tlc|excerpt|Ocean color|paragraphs{{=}}2-3|file{{=}}no}} // (example taken from [[Ocean#Color]]) you could code: : {{tlc|excerpt|Ocean color|paragraphs{{=}}2|file{{=}}no|inline{{=}}yes}} : {{tlc|excerpt|Ocean color|paragraphs{{=}}3|file{{=}}no|inline{{=}}yes}} and this will remove the [[Help:Line-break handling#Newlines|line break]] between the two paragraphs, so they will render as one paragraph. By default, an {{tl|excerpt}} generates an HTML [[div and span#Differences and default behaviour|div-tag]], which is a [[HTML element#Block elements|block-level display element]], so contiguous excerpts are normally separate block elements with line breaks between them. This can be overridden through use of param {{para|inline|yes}}, which suppresses the div-tag, and results in an [[HTML element#Inline elements|inline display element]] instead. In this case, just as with running text on adjacent lines of [[wikicode]], no line break is generated between them. This technique can also be adapted to [[#Differing citation styles|§&nbsp;change citation style]] or use different references. === Mitigating side effects of unexpected source file changes === {{anchor|HIDDENEXCERPTADVICE}}{{tsh|TM:HIDDENEXCERPTADVICE}} Excerpting content from a source page section into a target may cause [[WP:Link|link rot]] and other undesirable effects in the target if the heading or source content changes unexpectedly. Editors of the source page are generally unaware that editing the source can have side effects elsewhere. This situation can be mitigated by adding a [[WP:Hidden|hidden text]] message at the source page section being excerpted: : <code><nowiki><!-- Note: Content in this section is {{excerpt}}ed by "PageName#SectionName". See TM:HIDDENEXCERPTADVICE. --></nowiki></code> This alerts editors of the source file that part or all of the content in the area below the message is excerpted onto another page, so the editor can take target page side effects into account during their edit. Changing the heading is analogous to [[MOS:HIDDENLINKADVICE]], which describes how to avoid undesirable side effects when a section heading change may lead to a broken redirect. '''What to do''' If you are about to edit a section that contains a hidden excerpt advice message, what should you do? * If your prospective changes are typos, grammar, or wording adjustments '''without changing the section heading''', most likely you don't need to do anything * If you are changing the section heading, then this will likely break the {{t|excerpt}} and leave an error message at the target page. Have a look at the indicated source page and find the {{t|excerpt}} template. If it contains a pointer to the old section heading name on the source page section (see [[Template:Excerpt#Parameters|positional parameter 2]]), then you should update that parameter to the new section name. * If you are making a substantial change to excerpted content at the source in a way that may adversely affect the target page, you could: *# raise a discussion at the target Talk page, to advise editors about changes to the source; you could link [[TM:HIDDENEXCERPTADVICE]] in the discussion or in the edit summary *# replace the {{t|excerpt}} template in the target with content [[WP:CWW|copied]] from the version of the source before you changed it. (Don't forget to provide [[WP:CWW|copy attribution]] in the edit summary). This is a permanent solution for that excerpt, and you should remove the hidden link from the source page after completing it. == Advantages and disadvantages == The use of {{tl|Excerpt}} has the following advantages: * '''Reduces maintenance''' by avoiding duplicate content that must be updated multiple times * '''Improves content quality''' by encouraging editors to merge related content, rather than having multiple versions in various stages of development (see [[#Replacing summary section with excerpt of child article]]) * '''Fosters collaboration''' by channeling contributors into one place, rather than working in parallel * '''Promotes rapid development''' of articles, especially those written in [[WP:Summary style|summary style]] It also has the following disadvantages: * '''Impediment to editing''' as you have to go to the sub article to make changes to the main article (though excerpts include a hatnote with an [edit] button to edit the excerpted article in one click) * '''Reduces accuracy''' as an excerpt of one article is not always a perfect fit into a new article (but see [[#Refinement using inclusion control]]) * '''Decreases visibility''' as changes to the sub article will not appear on the watchlist of editors of the main article (see [[phab:T55525]]) * '''Risk of linkrot''' as pages or sections are blanked, moved, or deleted; this may result in the appearance of [[#Error messages|§&nbsp;error messages]] on the page (but see {{slink||Mitigating side effects of unexpected source file changes}} above; additionally, broken excerpts are automatically tracked at [[:Category:Articles with broken excerpts]] and regularly fixed). ==Incompatibilities== {{Replace | {{excerpt|Template:R/doc|Incompatibilities |hat=no}} | this template | template {{tl|R}} }} == Error messages == If an error is detected, an error message will appear in the article in place of the expected transcluded content: * <span class="error">No page given</span> – No page was passed to the template <!--error-no-page--> * <span class="error">Title X is not valid</span> – The title passed is not valid (contains [[Wikipedia:Naming conventions (technical restrictions)#Forbidden characters|forbidden characters]] such as < or >) <!--error-invalid-title--> * <span class="error">Page X not found</span> – The page passed does not exist, or the page is a redirect and the target page was not found <!--error-page-not-found--> * <span class="error">Lead section is empty</span> – The page exists, but cannot excerpt from non-existent lead <!--error-lead-empty--> * <span class="error">Section X not found</span> – The page exists, but cannot excerpt the desired section because either: ** The given section does not exist. <!--error-section-not-found--> This may occur if the source page section is removed or renamed. To help mitigate this, see [[MOS:BROKENSECTIONLINKS]]. ** The given section exists, but is excluded from transclusion by one of the [[Help:Template#Inclusion control: noinclude, includeonly, and onlyinclude|inclusion control]] tags. * <span class="error">Section X is empty</span> – The given section exists, but is empty <!--error-section-empty--> * <span class="error">Template loop detected</span> – The excerpted section contains itself an excerpt. While this is not strictly a template loop, the software considers it so. To fix it, skip the intermediate excerpt ([[Special:Diff/1184292340|example]]). == See also == * [[Module:Excerpt]] * [[Module:Excerpt/config]] * [[Module:WikitextParser]] * [[c:Data:I18n/Module:Excerpt.tab]] * [[Help:Transclusion#Drawbacks]] * [[:Category:Articles with excerpts]], or [https://en.wikipedia.org/w/index.php?title=Special%3AWhatLinksHere&target=Template%3AExcerpt&namespace=0 Articles that link to "Template:Excerpt"] (unsorted) * [[:Category:Articles with broken excerpts]] * [[Help:Labeled section transclusion]] – A more efficient method for simple section transclusions * [[Wikipedia:Transclusion#Selective transclusion]] – How to transclude one or more sections of an article or project page into another * [[Wikipedia:Summary style#Synchronization]] * [[Wikipedia:Manual of Style/Lead section]] * [[Wikipedia:WikiProject Introductions]] * {{tl|Transclude lead excerpt}} and {{tl|Transclude linked excerpt}} – Templates designed for excerpt transclusion in portals * {{tl|Transcluded section}} – Creates hatnote but ''does not transclude'' the section ** [https://en.wikipedia.org/w/index.php?title=Special%3AWhatLinksHere&target=Template%3ATranscluded+section&namespace=0 Pages that link to "Template:Transcluded section" (articles)] (unsorted) * {{tl|Transcluding article}} – Transcludes one or more entire pages ** [https://en.wikipedia.org/w/index.php?title=Special%3AWhatLinksHere&target=Template%3ATranscluding+article&namespace=0 Pages that link to "Template:Transcluding article" (articles)] (unsorted) * {{tl|Template parameter value}} – Extracts the value of a parameter passed to a template * [[meta:Grants:Project/Rapid/Sophivorus/Excerpts]] - Grant to spread excerpts to various Wikipedias * [[meta:Concise Wikipedia]] - Perennial new project proposal (see the [[meta:Concise Wikipedia#A summary of existing short-options, using an example|comparison table]] at the bottom in particular) * [[Single source of truth]] * [[Wikimania:2021:Submissions/Excerpts: Modular and Reusable Content within Wikipedia|Excerpts: Modular and Reusable Content within Wikipedia]] - Video presentation about excerpts for Wikimania 2021 == Template data == <templatedata> { "params": { "1": { "aliases": [ "article", "page" ], "label": "Article", "description": "Name of the article or page to transclude", "example": "Science", "type": "wiki-page-name", "required": true }, "2": { "aliases": [ "section", "fragment" ], "label": "Section", "description": "Name of the section or <section> tag to transclude", "example": "History", "type": "string" }, "paragraphs": { "label": "Paragraphs", "description": "Paragraphs to transclude", "example": "1-3,5", "type": "string", "aliases": [ "paragraph" ] }, "files": { "label": "Files", "description": "Files to transclude", "example": "1-3,5", "type": "string", "default": "1", "aliases": [ "file" ] }, "subsections": { "label": "Subsections", "description": "Whether to transclude the subsections of the requested section", "example": "yes", "type": "boolean" }, "tables": { "label": "Tables", "description": "Tables to transclude", "example": "Stats2020", "type": "string", "aliases": [ "table" ] }, "references": { "label": "References", "description": "Whether to transclude the references", "example": "no", "type": "boolean" }, "only": { "label": "Only", "description": "Transclude only this kind of element", "example": "table", "type": "string" }, "this": { "label": "This", "description": "Change the initial text of the hatnote", "example": "This gallery is", "type": "string" }, "displaytitle": { "label": "Display title", "description": "Change the text of the link in the hatnote", "type": "string" }, "class": { "label": "Class", "description": "Additional CSS class", "example": "noprint", "type": "string" }, "hat": { "label": "Hatnote", "description": "Whether to include the hatnote", "example": "no", "type": "boolean", "default": "yes" }, "bold": { "label": "Bold", "description": "Whether to preserve bold text", "example": "no", "type": "boolean", "default": "yes" }, "links": { "label": "Wikilinks", "description": "Whether to preserve wikilinks", "example": "no", "type": "boolean", "default": "yes" }, "quote": { "label": "Quote", "description": "Wraps the excerpt in <blockquote> tags", "example": "yes", "type": "boolean", "default": "no" }, "inline": { "label": "Inline", "description": "Remove the hatnote and <div> tags around the excerpt, to use it inside other text", "example": "yes", "type": "boolean", "default": "no" }, "lists": { "aliases": [ "list" ], "label": "Lists", "description": "Lists to transclude", "example": "1", "type": "string" }, "templates": { "aliases": [ "template" ], "label": "Templates", "description": "Templates to transclude", "example": "Infobox person", "type": "string" }, "onlyfreefiles": { "label": "Only free files", "description": "Disable transclusion of non-free files", "example": "no", "type": "boolean", "default": "yes" }, "briefdates": { "label": "Brief dates", "description": "Abbreviate birth and death information to (YYYY-YYYY) format", "example": "yes", "type": "boolean", "default": "no" } }, "description": "This template is used for transcluding part of an article into another article.", "paramOrder": [ "1", "2", "only", "paragraphs", "files", "tables", "lists", "templates", "references", "subsections", "hat", "bold", "links", "quote", "this", "displaytitle", "inline", "onlyfreefiles", "briefdates", "class" ] } </templatedata> <includeonly>{{sandbox other|| <!-- Categories go below this line; interwikis go to Wikidata. --> [[Category:Wikipedia page-section templates]] [[Category:Transclude page content templates]] }}</includeonly> 85nw1lji79re3n20seqao7iopnf39e1 797165 797164 2026-06-08T20:19:21Z SM7 3953 1 revision imported from [[:en:Template:Excerpt/doc]] 797164 wikitext text/x-wiki {{High-use}} {{Documentation subpage}} {{never subst}} {{Lua|Module:Excerpt}} This template is used for reusing parts of pages in other pages. This practice has various [[#Advantages and disadvantages|advantages and disadvantages]]. This template extends the capabilities of the built-in [[Help:Transclusion|normal transclusion]] and [[Help:Labeled section transclusion|labeled section transclusion]]. == Usage == * <code><nowiki>{{Excerpt|Page title}}</nowiki></code> – Transclude the lead section ([[Africa#Architecture|example]]) * <code><nowiki>{{Excerpt|Page title|Section title}}</nowiki></code> – Transclude a specific section, excluding any subsections ([[Eating#Mammals|example]]) == Parameters == There is one required parameter, and numerous optional ones for configuring the excerpt: === Summary === '''Source identification''' * {{para|1}} – Name of the article or page to transclude. '''Required.''' Aliases: {{para|article}} or {{para|page}}. * {{para|2}} – Name of the section or tag to transclude. Optional; if omitted, transcludes the lead section (content above the first section header). Aliases: {{para|section}} or {{para|fragment}}. '''Transclusion config''' Transcludable content is defined as one of several ''element types'': {{pval|file}}, {{pval|list}}, {{pval|paragraph}}, {{pval|reference}}, {{pval|subsection}}, {{pval|table}}, or {{pval|template}}. Config parameters specify which ''element type'' to transclude, and in some cases, ''how many'' and ''which'' items of that type to transclude. All config parameters are optional; if omitted, all items of all element types are transcluded from the source page identified by the two unnamed parameters. Some element types support conditional item transclusion by specifying an item number range (1-3) or comma series (1, 2, 5); these types include: files, lists, paragraphs, and tables. There are ten optional transclusion configuration parameters: * {{para|only}} – Element types to transclude. Values: {{pval|file(s)}}, {{pval|list(s)}}, {{pval|table(s)}}, {{pval|template(s)}}, {{pval|paragraph(s)}}. Default: all element types. * {{para|files}} – [[WP:FILE|Files]] to transclude. Default: all files. Same basic syntax as {{para|paragraphs}}, but see {{slink||Details}}. ** {{para|onlyfreefiles|no}} – Enables transclusion of [[WP:Non-free content|non-free files]]. Default: exclude non-free content. * {{para|lists}} – Lists ([[MOS:LIST#Bulleted lists|bulleted]], [[MOS:LIST#Numbered lists|numbered]]) to transclude. Default: all lists. Same syntax as for {{para|paragraphs}}. * {{para|paragraphs}} – [[H:PARAGRAPH|Paragraphs]] to transclude. Default: all paragraphs. * {{para|references|no}} – Exclude all [[WP:REF|references]] between <code><nowiki><ref>...</ref></nowiki></code> tags. * {{para|subsections|yes}} – Include [[MOS:SECTIONS|subsections]] of the transcluded section. Default: only content above the first subsection header. * {{para|tables}} – [[Help:Table|Tables]] to transclude. Default: all tables. Same basic syntax as {{para|paragraphs}}, but see {{slink||Details}}. * {{para|templates}} – [[Help:Template|Templates]] to transclude. By default all templates are transcluded, except those blacklisted at [[Module:Excerpt/config]]. See {{slink||Details}} for how to specify a specific template or templates for inclusion or exclusion. '''Style and extra features''' These optional parameters alter the way transcluded items are displayed: * {{para|bold|yes}} – Preserve '''bold''' text. * {{para|briefdates|yes}} – Abbreviate birth and death information to (YYYY-YYYY) format * {{para|displaytitle}} – Change the text of the link in the hatnote. For example, [[WP:ITHAT|add italics]], subscripts, etc. * {{para|hat|no}} – Hide the [[Wikipedia:Hatnote|hatnote]] "This section is an excerpt from..." * {{para|inline|yes}} – Remove the <code><nowiki><div></nowiki></code> tags around the excerpt, to use it inside other text, or to add references or other content after it [[#Suppress line breaks between paragraphs|with no paragraph break]] between them. Also hides the hatnote; to reestablish it, use {{tl|transclusion notice}} above the Excerpt invocation. * {{para|links|no}} – Unlink all [[H:WIKILINK|wikilinks]] and render as plain text. * {{para|quote|yes}} – Wrap the excerpt with <code><nowiki><blockquote></nowiki></code> tags. * {{para|this}} – Change the initial text of the hatnote. For example, if the transcluded content is a gallery, you can set {{para|this|This gallery is}} so that the hatnote reads "This gallery is an excerpt from..." === Details === * {{para|1}} or {{para|article}} or {{para|page}} *: By default the lead section is transcluded ([[Africa#Water|example]]). If the page contains an infobox, the image and caption of the infobox will be transcluded (unless {{para|files|0}} is set). Also, templates listed at [[Module:Excerpt/config]] will not be transcluded (unless requested explicitly with {{para|templates}}, see below). * {{para|2}} or {{para|section}} or {{para|fragment}} *: Name of the section to transclude ([[Eating#Mammals|example]]) or of the <code><nowiki><section></nowiki></code> tag to transclude ([[Axiom Space#Ax-1|example]]). In the case of a section tag, the section must be marked with <code><nowiki><section begin="Name of the fragment" /></nowiki></code> and <code><nowiki><section end="Name of the fragment" /></nowiki></code> in the page to be transcluded. Notice that this template provides other ways of targeting specific fragments of a page without having to resort to section tags. * {{para|only}} *: The ''element type'' to transclude, excluding all other types. By default all element types are transcluded. Param {{para|only}} is an exclusionary param, and excludes all other types of elements, except the one you name, so that for example, specifying {{para|only|paragraphs}} excludes all lists, tables, templates, and so on. Param values can be in the singular (e.g., {{para|only|paragraph}}) or plural (e.g., {{para|only|paragraphs}}) and mean different things: in the singular, only the first item of that element type is transcluded; in the plural, all items are. *:* {{para|only|file}} – Transclude only the first file (but no lists, paragraphs, tables, etc.) *:* {{para|only|files}} – Transclude all files (but nothing else) *:* {{para|only|filename}} – Transclude only the file name (Example.jpg) of the first file (and nothing else). If no file is found, "Noimage.png" will be returned. *:* {{para|only|list}} – Transclude only the first list, exclude all other element types *:* {{para|only|lists}} – Transclude all lists (but nothing else) *:* {{para|only|table}} – Transclude only the first table, exclude all other element types. Does not transclude table templates, use {{para|templates}} for that. *:* {{para|only|tables}} – Transclude all tables (but nothing else) ([[JKT48#Singles|example]]) *:* {{para|only|template}} – Transclude only the first template (excluding templates blacklisted at [[Module:Excerpt/config]], as well as all other element types) *:* {{para|only|templates}} – Transclude all templates (excluding blacklisted templates) (but nothing else) *:* {{para|only|paragraph}} – Transclude only the first paragraph, exclude all other element types *:* {{para|only|paragraphs}} – Transclude all paragraphs (but nothing else) * {{para|files}} *: An ''element item'' param, defining which files, such as images and other media, to transclude. Default: all files. *:* {{para|files|0}} – Transclude no files *:* {{para|files|A.jpg}} – Transclude the file named 'A.jpg' *:* {{para|files|A.jpg, B.png, C.gif}} – Transclude the files named 'A.jpg', 'B.png' and 'C.gif' *:* {{para|files|.+%.png}} – Transclude all PNG files *:* {{para|files|-A.jpg}} – Transclude all files except the one named 'A.jpg' *:* {{para|files|-A.jpg, B.png, C.gif}} – Transclude all files except the ones named 'A.jpg', 'B.png' and 'C.gif' *:* {{para|files|-.+%.png}} – Transclude all non-PNG files * {{para|paragraphs}} *: An ''element item'' param, defining which paragraphs to transclude. By default all paragraphs are transcluded. *:* {{para|paragraphs|0}} – Transclude no paragraphs *:* {{para|paragraphs|1}} – Transclude the first paragraph *:* {{para|paragraphs|2}} – Transclude the second paragraph *:* {{para|paragraphs|1,3}} – Transclude the first and third paragraphs *:* {{para|paragraphs|1-3}} – Transclude the first, second and third paragraphs *:* {{para|paragraphs|1-3,5}} – Transclude the first, second, third and fifth paragraphs *:* {{para|paragraphs|-1}} – Transclude all paragraphs except the first *:* {{para|paragraphs|-2}} – Transclude all paragraphs except the second *:* {{para|paragraphs|-1,3}} – Transclude all paragraphs except the first and third *:* {{para|paragraphs|-1-3}} – Transclude all paragraphs except the first, second and third *:* {{para|paragraphs|-1-3,5}} – Transclude all paragraphs except the first, second, third and fifth * {{para|subsections|yes}} *: An ''element item'' param, defining which subsections of the source to transclude. Default: only the portion of the source lying above the first subsection header. Notice that if the transclusion is done from a section level 3 in the transcluding page, and the transcluded subsections are also level 3, then transcluded subsections will show with the same hierarchy as the transcluding section, which may not be the desired outcome, so use with caution. * {{para|tables}} *: An ''element item'' param, defining which tables to transclude. Default: all tables. Does not transclude table templates just above the tables. Same syntax as when transcluding paragraphs, but also: *:* {{para|tables|Stats2020}} – Transclude the table with id 'Stats2020' *:* {{para|tables|Stats2020, Stats2019, Stats2018}} – Transclude the tables with ids 'Stats2020', 'Stats2019' and 'Stats2018' *:* {{para|tables|-Stats2020}} – Transclude all tables except the one with id 'Stats2020' *:* {{para|tables|-Stats2020, Stats2019, Stats2018}} – Transclude all tables except the ones with ids 'Stats2020', 'Stats2019' and 'Stats2018' * {{para|templates}} *: An ''element item'' param, defining which templates to transclude. Default: all templates except those blacklisted at [[Module:Excerpt/config]]. Using a hyphen (minus sign) before a comma-delimited list of templates excludes those templates from transclusion. *:* {{para|templates|-Ocean}} – Add the template 'Ocean' to the blacklist *:* {{para|templates|-Ocean, Nature}} – Add the templates 'Ocean' and 'Nature' to the blacklist *:* {{para|templates|Infobox person}} – Ignore the blacklist and transclude the template 'Infobox person' *:* {{para|templates|Infobox person, Ocean}} – Ignore the blacklist and transclude the templates 'Infobox person' and 'Ocean' *:* {{para|templates|.*}} – Ignore the blacklist and transclude all templates == Tips and how-to == === Compared to #section === {{Further|Help:Transclusion#Standard section transclusion}} For simple cases of transcluding sections of articles, the {{pf|section}}, {{pf|section-x}}, and {{pf|section-h}} (abbreviated {{pf|lst}}, {{pf|lstx}}, and {{pf|lsth}})) parser functions can be used instead of this template. {{pf|lsth|''article''|''fragmentname''}} will transclude the section of "''article''" with the header "''fragmentname''", and {{pf|lsth|''article''}} will transclude the lead section of "''article''". Excerpting only specific paragraphs can be done by marking up the source article with <code><nowiki><section begin=</nowiki>''<nowiki>fragmentname</nowiki>''<nowiki>/>...<section end=</nowiki>''<nowiki>fragmentname</nowiki>''<nowiki>/></nowiki></code> tags and using {{pf|lst|''article''|''fragmentname''}} to transclude those fragments, which is equivalent to using the {{para|fragment|''fragmentname''}} parameter with this template. {{pf|lsth|''article''|''fragmentname''}} can also be used to transclude everything ''but'' those fragments. The text will not be trimmed of excess whitespace, there will not be a header (equivalent to {{para|hat|no}}), and all files, templates, tables, references, and subsections will be included unless the source article is marked up with <code><nowiki><section begin=</nowiki>''<nowiki>fragmentname</nowiki>''<nowiki>/>...<section end=</nowiki>''<nowiki>fragmentname</nowiki>''<nowiki>/></nowiki></code>, {{tag|noinclude}}, or {{tag|onlyinclude}} tags. [[Help:Self link|Self links]] will appear in bold. === Citations === ==== Differing styles ==== {{Further|Wikipedia:Citing sources#Variation in citation methods}} It can happen that the source you want to excerpt contains footnotes in a [[WP:CITEVAR|different citation style]] than your article, and excerpting the source would cause a citation style mismatch, which is contrary to the guideline on [[Wikipedia:Citing sources|citing sources]]. Sometimes, excerpt can still be used, while avoiding a mismatch in style, by the use of params {{para|references|no}} and {{para|inline|yes}}. If the source you want to excerpt has multiple ref-tags interspersed throughout the source, and they need to display exactly in those locations in order to maintain full [[WP:V|verifiability]], then this source might not be a good candidate for transclusion via the {{tl|excerpt}} template, and [[WP:CWW|copying the content]] from the source into the article might be a better choice. However, you can still use the Excerpt template, if the source page you want to excerpt meets '''either''' of these conditions: * references are found mostly or all at the end of the text to be excerpted; or * references are scattered throughout, but could legitimately be regrouped at the end of the excerpt without adversely affecting [[WP:V|verifiability]]. In either case, use params {{para|references|no}} to strip the ref-tags from the transcluded content, and {{para|inline|yes}} to define the excerpt as an [[HTML element#Inline elements|inline display element]] in order to {{slink||Suppress line breaks between paragraphs}}, and then manually append a copy of all the citations in the source immediately after the excerpt tag ending curly braces in the target article, with no intervening line breaks, white space, or other characters between the tag and the appended references. The copied references will have to be manually converted from [[Wikipedia:Citing sources#Short and full citations|short footnote-style to full, inline citation-style]], or vice-versa, to match the citation style of the target. '''Note:''' citations are not creative content, and [[Wikipedia:Copying within Wikipedia#Where attribution is not needed|attribution is not needed]] for copying them. ==== Another approach ==== It is advised to seek consensus before attempting this layout change. If there are few shortened footnotes, a new subsection can be added to the "References" section to allow {{tl|sfn}} to link to the correct citations. Here is one approach, where the {{tl|sfn}} citations list in the excerpted article "SCBA" is titled "Works cited": <syntaxhighlight lang="wikitext" highlight="7-8"> == Transcluded section == {{excerpt|SCBA}} == References == {{reflist}} === From SCBA === {{excerpt|SCBA|Works cited|hat=0}} </syntaxhighlight> {{tl|sfn}} footnotes will now link to the citations created in the excerpted subsection. ==== Ref name collision ==== To prevent the possibility of collisions between [[WP:NAMEDREFS|named references]] in the transcluding and excerpted pages which would otherwise generate a [[Help:Cite errors/Cite error references duplicate key|duplicate key error]], all ref names in the excerpted content are altered by prefixing the name with the pagename of the excerpted page. Thus, if the ref on page {{kbd|MyArticle}} looks like <code><nowiki><ref name="Jones-2024"></nowiki></code>, then when excerpted by {{kbd|TargetPage}} it will be changed to <code><nowiki><ref name="MyArticle Jones-2024"></nowiki></code>, in order to avoid a collision with a possible "Jones-2024" ref already on the target page. === Excerpt trees === [[File:Excerpt tree.png|class=skin-invert-image|thumb|Visual representation of an imaginary excerpt tree.]] When a very general article uses excerpts from more specific articles, which in turn use excerpts from even more specific articles, then a [[tree structure]] emerges, called an "Excerpt tree". Here you can navigate the main excerpt trees on the English Wikipedia. It's useful for editors interested in expanding or improving them. To navigate the trees, click the following button<sup><abbr title="{{Int:gadgets-sister}}">(S)</abbr></sup>: {{Clickable button|url={{fullurl:{{FULLPAGENAME}}#Excerpt trees|withJS=MediaWiki:ExcerptTree.js}} |2=See the excerpt trees}} <div class="ExcerptTree" style="display: none;"> * [[January 6 United States Capitol attack]] * [[Algae]] * [[Africa]] * [[Aquatic ecosystem]] * [[Bangladesh]] * [[Campanology]] * [[Climate change in Africa]] * [[Climate change in South Asia]] * [[Climate justice]] * [[COVID-19 pandemic]] * [[Ecosystem]] ** [[Aquatic ecosystem]] * [[Effects of climate change on humans]] * [[Environmental impact of agriculture]] * [[Eutrophication]] * [[Fishing industry]] * [[Food]] * [[God]] ** [[Existence of God]] * [[Hygiene]] * [[List of carillons]] * [[List of LGBT-related cases in the United States Supreme Court]] * [[Marine debris]] * [[Middle East]] * [[Ocean]] * [[Plastic pollution]] * [[Public health]] * [[Sanitation]] * [[Sewage]] * [[Sustainable Development Goals]] * [[Tourism]] * [[Wastewater treatment]] * [[Water resources]] </div> === Refinement using inclusion control === {{Further|Help:Template#Noinclude, includeonly, and onlyinclude}} Sometimes, a passage will almost fit for a transclusion, but not quite. In these cases, you can edit the source page to add <code><nowiki><noinclude>...</noinclude></nowiki></code> tags around content you don't want in the excerpt and <code><nowiki><includeonly>...</includeonly></nowiki></code> tags around content you want only in the excerpt. For instance, the page [[COVID-19 misinformation]] begins with "The [[COVID-19 pandemic]] has resulted in [[misinformation]]...". However, when excerpting this lead to the misinformation section of [[COVID-19 pandemic]], we don't need to specify which pandemic we're referring to. Therefore, the code <code><nowiki>The <noinclude>[[COVID-19 pandemic]]</noinclude><includeonly>pandemic</includeonly> has resulted in [[misinformation]]</nowiki></code> can be used at the misinformation page, so that it will appear at the pandemic page as "The pandemic has resulted in [[misinformation]]...". For pages with a high volume of edits, it may be a good idea to leave a hidden comment explaining why the tags are there, so that no one will be [[WP:FENCE|tempted]] to remove them, like so: <code><nowiki>The <noinclude>[[COVID-19 pandemic]]</noinclude><!--These tags are used to refine the excerpt at [[COVID-19 pandemic]]--><includeonly>pandemic</includeonly> has resulted in [[misinformation]]</nowiki></code> Please note that when the <code><nowiki></noinclude></nowiki></code> tag is wrapped into a new line, a character next to it would be interpreted as a line beginning. This can lead to some formatting problems. For example, when a <code><nowiki></noinclude></nowiki></code> at line beginning is succeeded by a [[whitespace character]], the page engine would translate this as a [[Help:Wikitext#Limiting formatting / escaping wiki markup|leading space]] that renders the subsequent paragraph in [[code block]] and [[monospaced font]] with preserved formatting. For this reason, no spaces should separate the <code><nowiki></noinclude></nowiki></code> tag from the text it precedes. === Replacing summary section with excerpt of child article === [[File:How to excerpt.webm|thumb|How to replace a section with an excerpt.]] A section is often a [[WP:Summary style|summary]] in a [[WP:G#Parent article|parent article]] of a more detailed page about a subtopic located in a [[WP:G#child page|child page]]; these are generally linked with [[Template:Main]] in the parent. Sometimes it's convenient to replace the content of such a summary section in the parent with an excerpt of the child page lead (after merging any valuable content of the section into the child page). In such cases, an efficient way to proceed is: # Open the parent section for editing in one tab, and the child article in another. # Copy the text of the parent section and append it to the lead section of the child page. # Consolidate and adjust the combined lead using common sense. # Save the changes in the child article with an [[WP:ES|edit summary]] like: "{{xt|<nowiki>Copied content from [[Page]]. See that article's history for attribution."</nowiki>}} # Back in the parent page section, delete all content except the section header and replace it with an excerpt of the child page. # Save the changes in the section; proposed edit summary: "{{xt|Moved section content to <nowiki>[[Child page title]]</nowiki> and replaced with excerpt.}}" '''IMPORTANT!''' In step four, include the '''full''' edit summary as shown to comply with Wikipedia's [[Wikipedia:Copying within Wikipedia|copyright policy]]. If you forget to do this at the time of the original edit, follow the instructions on [[Wikipedia:Copying within Wikipedia#Repairing insufficient attribution|Repairing insufficient attribution]] to create a dummy edit with the required edit summary. === Suppress line breaks between paragraphs === If you want to merge two excerpted paragraphs from a source into one longer one in your article, use two excerpts instead of one, and change the display mode to [[HTML element#Inline elements|inline]]. So, for example, instead of this : : {{tlc|excerpt|Ocean color|paragraphs{{=}}2-3|file{{=}}no}} // (example taken from [[Ocean#Color]]) you could code: : {{tlc|excerpt|Ocean color|paragraphs{{=}}2|file{{=}}no|inline{{=}}yes}} : {{tlc|excerpt|Ocean color|paragraphs{{=}}3|file{{=}}no|inline{{=}}yes}} and this will remove the [[Help:Line-break handling#Newlines|line break]] between the two paragraphs, so they will render as one paragraph. By default, an {{tl|excerpt}} generates an HTML [[div and span#Differences and default behaviour|div-tag]], which is a [[HTML element#Block elements|block-level display element]], so contiguous excerpts are normally separate block elements with line breaks between them. This can be overridden through use of param {{para|inline|yes}}, which suppresses the div-tag, and results in an [[HTML element#Inline elements|inline display element]] instead. In this case, just as with running text on adjacent lines of [[wikicode]], no line break is generated between them. This technique can also be adapted to [[#Differing citation styles|§&nbsp;change citation style]] or use different references. === Mitigating side effects of unexpected source file changes === {{anchor|HIDDENEXCERPTADVICE}}{{tsh|TM:HIDDENEXCERPTADVICE}} Excerpting content from a source page section into a target may cause [[WP:Link|link rot]] and other undesirable effects in the target if the heading or source content changes unexpectedly. Editors of the source page are generally unaware that editing the source can have side effects elsewhere. This situation can be mitigated by adding a [[WP:Hidden|hidden text]] message at the source page section being excerpted: : <code><nowiki><!-- Note: Content in this section is {{excerpt}}ed by "PageName#SectionName". See TM:HIDDENEXCERPTADVICE. --></nowiki></code> This alerts editors of the source file that part or all of the content in the area below the message is excerpted onto another page, so the editor can take target page side effects into account during their edit. Changing the heading is analogous to [[MOS:HIDDENLINKADVICE]], which describes how to avoid undesirable side effects when a section heading change may lead to a broken redirect. '''What to do''' If you are about to edit a section that contains a hidden excerpt advice message, what should you do? * If your prospective changes are typos, grammar, or wording adjustments '''without changing the section heading''', most likely you don't need to do anything * If you are changing the section heading, then this will likely break the {{t|excerpt}} and leave an error message at the target page. Have a look at the indicated source page and find the {{t|excerpt}} template. If it contains a pointer to the old section heading name on the source page section (see [[Template:Excerpt#Parameters|positional parameter 2]]), then you should update that parameter to the new section name. * If you are making a substantial change to excerpted content at the source in a way that may adversely affect the target page, you could: *# raise a discussion at the target Talk page, to advise editors about changes to the source; you could link [[TM:HIDDENEXCERPTADVICE]] in the discussion or in the edit summary *# replace the {{t|excerpt}} template in the target with content [[WP:CWW|copied]] from the version of the source before you changed it. (Don't forget to provide [[WP:CWW|copy attribution]] in the edit summary). This is a permanent solution for that excerpt, and you should remove the hidden link from the source page after completing it. == Advantages and disadvantages == The use of {{tl|Excerpt}} has the following advantages: * '''Reduces maintenance''' by avoiding duplicate content that must be updated multiple times * '''Improves content quality''' by encouraging editors to merge related content, rather than having multiple versions in various stages of development (see [[#Replacing summary section with excerpt of child article]]) * '''Fosters collaboration''' by channeling contributors into one place, rather than working in parallel * '''Promotes rapid development''' of articles, especially those written in [[WP:Summary style|summary style]] It also has the following disadvantages: * '''Impediment to editing''' as you have to go to the sub article to make changes to the main article (though excerpts include a hatnote with an [edit] button to edit the excerpted article in one click) * '''Reduces accuracy''' as an excerpt of one article is not always a perfect fit into a new article (but see [[#Refinement using inclusion control]]) * '''Decreases visibility''' as changes to the sub article will not appear on the watchlist of editors of the main article (see [[phab:T55525]]) * '''Risk of linkrot''' as pages or sections are blanked, moved, or deleted; this may result in the appearance of [[#Error messages|§&nbsp;error messages]] on the page (but see {{slink||Mitigating side effects of unexpected source file changes}} above; additionally, broken excerpts are automatically tracked at [[:Category:Articles with broken excerpts]] and regularly fixed). ==Incompatibilities== {{Replace | {{excerpt|Template:R/doc|Incompatibilities |hat=no}} | this template | template {{tl|R}} }} == Error messages == If an error is detected, an error message will appear in the article in place of the expected transcluded content: * <span class="error">No page given</span> – No page was passed to the template <!--error-no-page--> * <span class="error">Title X is not valid</span> – The title passed is not valid (contains [[Wikipedia:Naming conventions (technical restrictions)#Forbidden characters|forbidden characters]] such as < or >) <!--error-invalid-title--> * <span class="error">Page X not found</span> – The page passed does not exist, or the page is a redirect and the target page was not found <!--error-page-not-found--> * <span class="error">Lead section is empty</span> – The page exists, but cannot excerpt from non-existent lead <!--error-lead-empty--> * <span class="error">Section X not found</span> – The page exists, but cannot excerpt the desired section because either: ** The given section does not exist. <!--error-section-not-found--> This may occur if the source page section is removed or renamed. To help mitigate this, see [[MOS:BROKENSECTIONLINKS]]. ** The given section exists, but is excluded from transclusion by one of the [[Help:Template#Inclusion control: noinclude, includeonly, and onlyinclude|inclusion control]] tags. * <span class="error">Section X is empty</span> – The given section exists, but is empty <!--error-section-empty--> * <span class="error">Template loop detected</span> – The excerpted section contains itself an excerpt. While this is not strictly a template loop, the software considers it so. To fix it, skip the intermediate excerpt ([[Special:Diff/1184292340|example]]). == See also == * [[Module:Excerpt]] * [[Module:Excerpt/config]] * [[Module:WikitextParser]] * [[c:Data:I18n/Module:Excerpt.tab]] * [[Help:Transclusion#Drawbacks]] * [[:Category:Articles with excerpts]], or [https://en.wikipedia.org/w/index.php?title=Special%3AWhatLinksHere&target=Template%3AExcerpt&namespace=0 Articles that link to "Template:Excerpt"] (unsorted) * [[:Category:Articles with broken excerpts]] * [[Help:Labeled section transclusion]] – A more efficient method for simple section transclusions * [[Wikipedia:Transclusion#Selective transclusion]] – How to transclude one or more sections of an article or project page into another * [[Wikipedia:Summary style#Synchronization]] * [[Wikipedia:Manual of Style/Lead section]] * [[Wikipedia:WikiProject Introductions]] * {{tl|Transclude lead excerpt}} and {{tl|Transclude linked excerpt}} – Templates designed for excerpt transclusion in portals * {{tl|Transcluded section}} – Creates hatnote but ''does not transclude'' the section ** [https://en.wikipedia.org/w/index.php?title=Special%3AWhatLinksHere&target=Template%3ATranscluded+section&namespace=0 Pages that link to "Template:Transcluded section" (articles)] (unsorted) * {{tl|Transcluding article}} – Transcludes one or more entire pages ** [https://en.wikipedia.org/w/index.php?title=Special%3AWhatLinksHere&target=Template%3ATranscluding+article&namespace=0 Pages that link to "Template:Transcluding article" (articles)] (unsorted) * {{tl|Template parameter value}} – Extracts the value of a parameter passed to a template * [[meta:Grants:Project/Rapid/Sophivorus/Excerpts]] - Grant to spread excerpts to various Wikipedias * [[meta:Concise Wikipedia]] - Perennial new project proposal (see the [[meta:Concise Wikipedia#A summary of existing short-options, using an example|comparison table]] at the bottom in particular) * [[Single source of truth]] * [[Wikimania:2021:Submissions/Excerpts: Modular and Reusable Content within Wikipedia|Excerpts: Modular and Reusable Content within Wikipedia]] - Video presentation about excerpts for Wikimania 2021 == Template data == <templatedata> { "params": { "1": { "aliases": [ "article", "page" ], "label": "Article", "description": "Name of the article or page to transclude", "example": "Science", "type": "wiki-page-name", "required": true }, "2": { "aliases": [ "section", "fragment" ], "label": "Section", "description": "Name of the section or <section> tag to transclude", "example": "History", "type": "string" }, "paragraphs": { "label": "Paragraphs", "description": "Paragraphs to transclude", "example": "1-3,5", "type": "string", "aliases": [ "paragraph" ] }, "files": { "label": "Files", "description": "Files to transclude", "example": "1-3,5", "type": "string", "default": "1", "aliases": [ "file" ] }, "subsections": { "label": "Subsections", "description": "Whether to transclude the subsections of the requested section", "example": "yes", "type": "boolean" }, "tables": { "label": "Tables", "description": "Tables to transclude", "example": "Stats2020", "type": "string", "aliases": [ "table" ] }, "references": { "label": "References", "description": "Whether to transclude the references", "example": "no", "type": "boolean" }, "only": { "label": "Only", "description": "Transclude only this kind of element", "example": "table", "type": "string" }, "this": { "label": "This", "description": "Change the initial text of the hatnote", "example": "This gallery is", "type": "string" }, "displaytitle": { "label": "Display title", "description": "Change the text of the link in the hatnote", "type": "string" }, "class": { "label": "Class", "description": "Additional CSS class", "example": "noprint", "type": "string" }, "hat": { "label": "Hatnote", "description": "Whether to include the hatnote", "example": "no", "type": "boolean", "default": "yes" }, "bold": { "label": "Bold", "description": "Whether to preserve bold text", "example": "no", "type": "boolean", "default": "yes" }, "links": { "label": "Wikilinks", "description": "Whether to preserve wikilinks", "example": "no", "type": "boolean", "default": "yes" }, "quote": { "label": "Quote", "description": "Wraps the excerpt in <blockquote> tags", "example": "yes", "type": "boolean", "default": "no" }, "inline": { "label": "Inline", "description": "Remove the hatnote and <div> tags around the excerpt, to use it inside other text", "example": "yes", "type": "boolean", "default": "no" }, "lists": { "aliases": [ "list" ], "label": "Lists", "description": "Lists to transclude", "example": "1", "type": "string" }, "templates": { "aliases": [ "template" ], "label": "Templates", "description": "Templates to transclude", "example": "Infobox person", "type": "string" }, "onlyfreefiles": { "label": "Only free files", "description": "Disable transclusion of non-free files", "example": "no", "type": "boolean", "default": "yes" }, "briefdates": { "label": "Brief dates", "description": "Abbreviate birth and death information to (YYYY-YYYY) format", "example": "yes", "type": "boolean", "default": "no" } }, "description": "This template is used for transcluding part of an article into another article.", "paramOrder": [ "1", "2", "only", "paragraphs", "files", "tables", "lists", "templates", "references", "subsections", "hat", "bold", "links", "quote", "this", "displaytitle", "inline", "onlyfreefiles", "briefdates", "class" ] } </templatedata> <includeonly>{{sandbox other|| <!-- Categories go below this line; interwikis go to Wikidata. --> [[Category:Wikipedia page-section templates]] [[Category:Transclude page content templates]] }}</includeonly> 85nw1lji79re3n20seqao7iopnf39e1 शिक्षण बिधि 0 101019 797166 2026-06-08T20:23:42Z SM7 3953 नया आधार लेख 797166 wikitext text/x-wiki '''शिक्षण विधि''' भा '''पढ़ावे के तरीका''' ({{Langx|en|Teaching Method}}) सिद्धांत आ तरीका सभ के एगो समूह हवे, जवना के इस्तेमाल टीचर विद्यार्थी के सीखला में मदद करे खातिर करेलन। ई रणनीति सभ कुछ हद तक पढ़ावल जाए वाला विषय, कुछ हद तक विद्यार्थी के ज्ञान आ दक्षता, आ कुछ हद तक सीखला के वातावरण से पैदा होखे वाली सीमाजन के आधार पर तय होले। कवनो शिक्षण विधि तबे उपयुक्त आ प्रभावी मानल जाले जब ऊ विद्यार्थी, विषयवस्तु के प्रकृति आ अपेक्षित सीखला के परिणाम के ध्यान में रखे। शिक्षण के तरीका के मोटे तौर पर शिक्षक-केंद्रित आ विद्यार्थी-केंद्रित दृष्टिकोण में बाँटल जा सकेला। हालाँकि व्यवहार में शिक्षक अक्सर विद्यार्थी के पूर्व ज्ञान, अनुभव आ सीखला के लक्ष्य के अनुसार एह दुनो तरीका के बीच संतुलन बनावत रहेलन। शिक्षक-केंद्रित शिक्षण में शिक्षक मुख्य अधिकार प्राप्त व्यक्ति होलें। एह मॉडल में विद्यार्थी के अक्सर अइसन व्यक्ति के रूप में देखल जाला जे मुख्य रूप से व्याख्यान आ प्रत्यक्ष निर्देश के माध्यम से जानकारी ग्रहण करेला। एह तरीका में शिक्षक के प्रमुख भूमिका ज्ञान आ सूचना के हस्तांतरण कइल होला। शिक्षण आ मूल्यांकन के अलग-अलग प्रक्रिया मानल जाला, आ विद्यार्थी के उपलब्धि के माप आमतौर पर लिखित परीक्षा आ वस्तुनिष्ठ मूल्यांकन के माध्यम से कइल जाला। एकरा विपरीत, विद्यार्थी-केंद्रित शिक्षण में शिक्षक आ विद्यार्थी दुनो सीखला के प्रक्रिया में सक्रिय भूमिका निभावेलन। एह तरीका के कई बेर अधिकारपूर्ण (ऑथोरिटेटिव) दृष्टिकोण भी कहल जाला। एहमें शिक्षक के मुख्य काम मार्गदर्शक आ सहायक के रूप में विद्यार्थी के सीखला आ समझ विकसित करे में मदद कइल होला। विद्यार्थी के प्रगति के मूल्यांकन औपचारिक आ अनौपचारिक दुनो माध्यम से कइल जाला, जइसे समूह परियोजना, विद्यार्थी पोर्टफोलियो आ कक्षा में सहभागिता। एह मॉडल में शिक्षण आ मूल्यांकन एक-दूसरा से जुड़ल रहेला, आ शिक्षक पढ़ावे के दौरान लगातार विद्यार्थी के सीखला के आकलन करत रहेलन। {{edu-stub}} cju2j07009t6gvj4wmrjeewm3tw1jfa Module:Excerpt 828 101020 797168 2018-04-22T16:45:02Z en>Certes 0 Module to extract the lead from an article. Unlike #lsth, it removes infoboxes, hatnotes, etc. 797168 Scribunto text/plain local p = {} -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagename) -- Find the lead section of the named page -- %b{} removes any leading -- If the page exists, protected or not, this is some other value -- Note: this check does NOT record a wikilink or transclusion from the calling page to pagename title=mw.title.new(pagename) text=title.getContent(title) text=mw.ustring.gsub(text,"%c%s*==.*","") -- remove first heading and everything after it text=mw.ustring.gsub(text,"<noinclude>.-</noinclude>","") -- remove noinclude bits text=mw.ustring.gsub(text,"^%A*%b{}%s*","") -- remove infoboxes, hatnotes, tags, etc. from the front text=mw.ustring.gsub(text,"<ref.->.-</ref>","") -- remove refs text=mw.ustring.match(text,"%C*'''.*") or text -- start at the first line with bold text, if any return text; end -- Entry point for template callers using #invoke: function p.lead(frame) -- args = { 1 = page name } local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template return frame:preprocess(p._lead(args[1] or pargs[1])); end return p 32s80hptyxfu9p5f59kftzemtxmexdi 797169 797168 2018-04-22T17:14:41Z en>Certes 0 Fix comments; return blank string on certain errors 797169 Scribunto text/plain local p = {} -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagename) if not pagename then return "" end title=mw.title.new(pagename) -- Find the lead section of the named page if not title then return "" end text=title.getContent(title) or "" text=mw.ustring.gsub(text,"%c%s*==.*","") -- remove first heading and everything after it text=mw.ustring.gsub(text,"<noinclude>.-</noinclude>","") -- remove noinclude bits text=mw.ustring.gsub(text,"^%A*%b{}%s*","") -- remove infoboxes, hatnotes, tags, etc. from the front text=mw.ustring.gsub(text,"<ref.->.-</ref>","") -- remove refs text=mw.ustring.match(text,"%C*'''.*") or text -- start at the first line with bold text, if any return text end -- Entry point for template callers using #invoke: function p.lead(frame) -- args = { 1 = page name } local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template return frame:preprocess(p._lead(args[1] or pargs[1])) end return p 1398vdi87pexw8a4xh1766n9cr3i6ee 797170 797169 2018-04-24T10:24:44Z en>Certes 0 Remove <ref name="Foo" /> for ref cited elsewhere 797170 Scribunto text/plain local p = {} -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagename) if not pagename then return "" end title=mw.title.new(pagename) -- Find the lead section of the named page if not title then return "" end text=title.getContent(title) or "" text=mw.ustring.gsub(text,"%c%s*==.*","") -- remove first heading and everything after it text=mw.ustring.gsub(text,"<noinclude>.-</noinclude>","") -- remove noinclude bits text=mw.ustring.gsub(text,"^%A*%b{}%s*","") -- remove infoboxes, hatnotes, tags, etc. from the front text=mw.ustring.gsub(text,"<%s*ref[^>]-/%s*>","") -- remove refs cited elsewhere text=mw.ustring.gsub(text,"<%s*ref.->.-<%s*/%s*ref%s*>","") -- remove refs text=mw.ustring.match(text,"%C*'''.*") or text -- start at the first line with bold text, if any return text end -- Entry point for template callers using #invoke: function p.lead(frame) -- args = { 1 = page name } local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template return frame:preprocess(p._lead(args[1] or pargs[1])) end return p 7fbgxob315aygps0lsew1s7ur6gnu7r 797171 797170 2018-04-24T13:46:43Z en>Certes 0 Fix: ensure multiple templates are removed from start. 797171 Scribunto text/plain local p = {} -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagename) if not pagename then return "" end title=mw.title.new(pagename) -- Find the lead section of the named page if not title then return "" end text=title.getContent(title) or "" text=mw.ustring.gsub(text,"%c%s*==.*","") -- remove first heading and everything after it text=mw.ustring.gsub(text,"<noinclude>.-</noinclude>","") -- remove noinclude bits repeat oldtext=text text=mw.ustring.gsub(text,"^%A-%b{}%s*","") -- remove infoboxes, hatnotes, tags, etc. from the front until text==oldtext text=mw.ustring.gsub(text,"<%s*ref[^>]-/%s*>","") -- remove refs cited elsewhere text=mw.ustring.gsub(text,"<%s*ref.->.-<%s*/%s*ref%s*>","") -- remove refs text=mw.ustring.match(text,"%C*'''.*") or text -- start at the first line with bold text, if any return text end -- Entry point for template callers using #invoke: function p.lead(frame) -- args = { 1 = page name } local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template return frame:preprocess(p._lead(args[1] or pargs[1])) end return p jfetv6doezr406o093wvhjnl5kgy7cs 797172 797171 2018-04-25T10:45:34Z en>Certes 0 Remove footnotes {{efn|not valid in Tennessee}} etc. 797172 Scribunto text/plain local p = {} -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagename) if not pagename then return "" end title=mw.title.new(pagename) -- Find the lead section of the named page if not title then return "" end text=title.getContent(title) or "" text=mw.ustring.gsub(text,"%c%s*==.*","") -- remove first heading and everything after it text=mw.ustring.gsub(text,"<noinclude>.-</noinclude>","") -- remove noinclude bits repeat oldtext=text text=mw.ustring.gsub(text,"^%A-%b{}%s*","") -- remove infoboxes, hatnotes, tags, etc. from the front until text==oldtext text=mw.ustring.gsub(text,"{{%s*[Ee]fn%s*|.-}}","") -- remove {{efn|footnote}} text=mw.ustring.gsub(text,"{{%s*[Ee]fn-la%s*|.-}}","") -- {{efn-la}} alias for {{efn}} text=mw.ustring.gsub(text,"{{%s*[Ee]l[mn]%s*|.-}}","") -- {{elm}} and {{eln}} alias for {{efn}} text=mw.ustring.gsub(text,"<%s*ref[^>]-/%s*>","") -- remove refs cited elsewhere text=mw.ustring.gsub(text,"<%s*ref.->.-<%s*/%s*ref%s*>","") -- remove refs text=mw.ustring.match(text,"%C*'''.*") or text -- start at the first line with bold text, if any return text end -- Entry point for template callers using #invoke: function p.lead(frame) -- args = { 1 = page name } local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template return frame:preprocess(p._lead(args[1] or pargs[1])) end return p fvb7we48ifxnzbf8f05x7gfh37g7w6j 797173 797172 2018-04-25T11:46:33Z en>Certes 0 Add paragraphs= argument; improve layout 797173 Scribunto text/plain local p = {} -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagename, paragraphlist) if not pagename then return "" end -- Return blank text rather than an error splurge local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return "" end local text = title.getContent(title) or "" text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits repeat local oldtext=text text = mw.ustring.gsub(text,"^%A-%b{}%s*","") -- remove infoboxes, hatnotes, tags, etc. from the front until text == oldtext if #paragraphlist > 0 then -- limit to requested paragraphs e.g. {1, 3, 4, 5} local paras = mw.text.split(text, "\n%s*\n") -- %s* may include \n if three or more appear together local sep="" -- no separator before first paragraph local newtext = "" for _, p in pairs(paragraphlist) do newtext = newtext .. sep .. paras[p] sep = "\n\n" end text = newtext end text = mw.ustring.gsub(text, "{{%s*[Ee]fn%s*|.-}}", "") -- remove {{efn|footnote}} text = mw.ustring.gsub(text, "{{%s*[Ee]fn-la%s*|.-}}", "") -- {{efn-la}} alias for {{efn}} text = mw.ustring.gsub(text, "{{%s*[Ee]l[mn]%s*|.-}}", "") -- {{elm}} and {{eln}} alias for {{efn}} text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.match(text, "%C*'''.*") or text -- start at the first line with bold text, if any return text end -- Entry point for template callers using #invoke: function p.lead(frame) -- args = { 1 = page name, paragraphs = list e.g. "1,3-5" } local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local parasets = mw.text.split(args["paragraphs"] or pargs["paragraphs"] or "", ",") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} local paralist = {} for _, ps in pairs(parasets) do local min, max = mw.ustring.match(ps,"^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(ps,"^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do table.insert(paralist, p) end end end return frame:preprocess(p._lead(args[1] or pargs[1], paralist)) end return p ac9vgvfbtxg46jmy5lqn75zuuz02m58 797174 797173 2018-04-25T12:19:55Z en>Certes 0 Allow page name to be a wikilink 797174 Scribunto text/plain local p = {} -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagename, paragraphlist) if not pagename then return "" end -- Return blank text rather than an error splurge local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return "" end local text = title.getContent(title) or "" text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits repeat local oldtext=text text = mw.ustring.gsub(text,"^%A-%b{}%s*","") -- remove infoboxes, hatnotes, tags, etc. from the front until text == oldtext if #paragraphlist > 0 then -- limit to requested paragraphs e.g. {1, 3, 4, 5} local paras = mw.text.split(text, "\n%s*\n") -- %s* may include \n if three or more appear together local sep="" -- no separator before first paragraph local newtext = "" for _, p in pairs(paragraphlist) do newtext = newtext .. sep .. paras[p] sep = "\n\n" end text = newtext end text = mw.ustring.gsub(text, "{{%s*[Ee]fn%s*|.-}}", "") -- remove {{efn|footnote}} text = mw.ustring.gsub(text, "{{%s*[Ee]fn-la%s*|.-}}", "") -- {{efn-la}} alias for {{efn}} text = mw.ustring.gsub(text, "{{%s*[Ee]l[mn]%s*|.-}}", "") -- {{elm}} and {{eln}} alias for {{efn}} text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.match(text, "%C*'''.*") or text -- start at the first line with bold text, if any return text end -- Entry point for template callers using #invoke: function p.lead(frame) -- args = { 1 = page name, paragraphs = list e.g. "1,3-5" } local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagename = args[1] or pargs[1] or "" pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" local parasets = mw.text.split(args["paragraphs"] or pargs["paragraphs"] or "", ",") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} local paralist = {} for _, ps in pairs(parasets) do local min, max = mw.ustring.match(ps,"^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(ps,"^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do table.insert(paralist, p) end end end return frame:preprocess(p._lead(pagename, paralist)) end return p 7zdpasicxy54dzsmzlxrvrqyqp1lgsp 797175 797174 2018-04-25T12:50:09Z en>Certes 0 Calling getContent more properly (no functional difference) 797175 Scribunto text/plain local p = {} -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagename, paragraphlist) if not pagename then return "" end -- Return blank text rather than an error splurge local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return "" end local text = title:getContent() or "" text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits repeat local oldtext=text text = mw.ustring.gsub(text,"^%A-%b{}%s*","") -- remove infoboxes, hatnotes, tags, etc. from the front until text == oldtext if #paragraphlist > 0 then -- limit to requested paragraphs e.g. {1, 3, 4, 5} local paras = mw.text.split(text, "\n%s*\n") -- %s* may include \n if three or more appear together local sep="" -- no separator before first paragraph local newtext = "" for _, p in pairs(paragraphlist) do newtext = newtext .. sep .. paras[p] sep = "\n\n" end text = newtext end text = mw.ustring.gsub(text, "{{%s*[Ee]fn%s*|.-}}", "") -- remove {{efn|footnote}} text = mw.ustring.gsub(text, "{{%s*[Ee]fn-la%s*|.-}}", "") -- {{efn-la}} alias for {{efn}} text = mw.ustring.gsub(text, "{{%s*[Ee]l[mn]%s*|.-}}", "") -- {{elm}} and {{eln}} alias for {{efn}} text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.match(text, "%C*'''.*") or text -- start at the first line with bold text, if any return text end -- Entry point for template callers using #invoke: function p.lead(frame) -- args = { 1 = page name, paragraphs = list e.g. "1,3-5" } local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagename = args[1] or pargs[1] or "" pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" local parasets = mw.text.split(args["paragraphs"] or pargs["paragraphs"] or "", ",") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} local paralist = {} for _, ps in pairs(parasets) do local min, max = mw.ustring.match(ps,"^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(ps,"^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do table.insert(paralist, p) end end end return frame:preprocess(p._lead(pagename, paralist)) end return p t0wn1m5e59slol1s1ljymgooq4o81bz 797176 797175 2018-04-25T12:56:31Z en>Certes 0 Do not try to append paragraphs whose numbers which exceed the number actually present 797176 Scribunto text/plain local p = {} -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagename, paragraphlist) if not pagename then return "" end -- Return blank text rather than an error splurge local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return "" end local text = title:getContent() or "" text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits repeat local oldtext=text text = mw.ustring.gsub(text,"^%A-%b{}%s*","") -- remove infoboxes, hatnotes, tags, etc. from the front until text == oldtext if #paragraphlist > 0 then -- limit to requested paragraphs e.g. {1, 3, 4, 5} local paras = mw.text.split(text, "\n%s*\n") -- %s* may include \n if three or more appear together local sep="" -- no separator before first paragraph local newtext = "" for _, p in pairs(paragraphlist) do if paras[p] then newtext = newtext .. sep .. paras[p] end -- else p exceeds number of paragraphs found sep = "\n\n" end text = newtext end text = mw.ustring.gsub(text, "{{%s*[Ee]fn%s*|.-}}", "") -- remove {{efn|footnote}} text = mw.ustring.gsub(text, "{{%s*[Ee]fn-la%s*|.-}}", "") -- {{efn-la}} alias for {{efn}} text = mw.ustring.gsub(text, "{{%s*[Ee]l[mn]%s*|.-}}", "") -- {{elm}} and {{eln}} alias for {{efn}} text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.match(text, "%C*'''.*") or text -- start at the first line with bold text, if any return text end -- Entry point for template callers using #invoke: function p.lead(frame) -- args = { 1 = page name, paragraphs = list e.g. "1,3-5" } local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagename = args[1] or pargs[1] or "" pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" local parasets = mw.text.split(args["paragraphs"] or pargs["paragraphs"] or "", ",") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} local paralist = {} for _, ps in pairs(parasets) do local min, max = mw.ustring.match(ps,"^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(ps,"^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do table.insert(paralist, p) end end end return frame:preprocess(p._lead(pagename, paralist)) end return p 016jvxj0n7184m770zob1wuhsrjqb9a 797177 797176 2018-04-25T20:21:28Z en>Certes 0 Get rid of more ref and footnote templates 797177 Scribunto text/plain local p = {} -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagename, paragraphlist) if not pagename then return "" end -- Return blank text rather than an error splurge local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return "" end local text = title:getContent() or "" text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits repeat local oldtext=text text = mw.ustring.gsub(text,"^%A-%b{}%s*","") -- remove infoboxes, hatnotes, tags, etc. from the front until text == oldtext if #paragraphlist > 0 then -- limit to requested paragraphs e.g. {1, 3, 4, 5} local paras = mw.text.split(text, "\n%s*\n") -- %s* may include \n if three or more appear together local sep="" -- no separator before first paragraph local newtext = "" for _, p in pairs(paragraphlist) do if paras[p] then newtext = newtext .. sep .. paras[p] end -- else p exceeds number of paragraphs found sep = "\n\n" end text = newtext end for _, t in pairs {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]", "[Ss]fn[bp]", "[Ss]fb"} do -- remove refs and footnotes text = mw.ustring.gsub(text, "{{%s*" .. t .. "%s*|.-}}", "") end text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.match(text, "%C*'''.*") or text -- start at the first line with bold text, if any return text end -- Entry point for template callers using #invoke: function p.lead(frame) -- args = { 1 = page name, paragraphs = list e.g. "1,3-5" } local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagename = args[1] or pargs[1] or "" pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" local parasets = mw.text.split(args["paragraphs"] or pargs["paragraphs"] or "", ",") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} local paralist = {} for _, ps in pairs(parasets) do local min, max = mw.ustring.match(ps,"^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(ps,"^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do table.insert(paralist, p) end end end return frame:preprocess(p._lead(pagename, paralist)) end return p ergg2fuofrfxfs7n3g5vlipz8y1umuj 797178 797177 2018-04-25T20:51:08Z en>Certes 0 Remove files and images. Remove initial white space rather than seeking bold text. 797178 Scribunto text/plain local p = {} -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagename, paragraphlist) if not pagename then return "" end -- Return blank text rather than an error splurge local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return "" end local text = title:getContent() or "" text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits repeat local oldtext=text text = mw.ustring.gsub(text,"^%A-%b{}%s*","") -- remove infoboxes, hatnotes, tags, etc. from the front until text == oldtext if #paragraphlist > 0 then -- limit to requested paragraphs e.g. {1, 3, 4, 5} local paras = mw.text.split(text, "\n%s*\n") -- %s* may include \n if three or more appear together local sep="" -- no separator before first paragraph local newtext = "" for _, p in pairs(paragraphlist) do if paras[p] then newtext = newtext .. sep .. paras[p] end -- else p exceeds number of paragraphs found sep = "\n\n" end text = newtext end for _, t in pairs {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]", "[Ss]fn[bp]", "[Ss]fb"} do -- remove refs and footnotes text = mw.ustring.gsub(text, "{{%s*" .. t .. "%s*|.-}}", "") end text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "%[%[%s*[Ff]ile%s*:%C*%]%]", "") -- remove files text = mw.ustring.gsub(text, "%[%[%s*[Ii]mage%s*:%C*%]%]", "") -- remove images text = mw.ustring.gsub(text, "^%s*", "") -- remove initial white space return text end -- Entry point for template callers using #invoke: function p.lead(frame) -- args = { 1 = page name, paragraphs = list e.g. "1,3-5" } local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagename = args[1] or pargs[1] or "" pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" local parasets = mw.text.split(args["paragraphs"] or pargs["paragraphs"] or "", ",") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} local paralist = {} for _, ps in pairs(parasets) do local min, max = mw.ustring.match(ps,"^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(ps,"^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do table.insert(paralist, p) end end end return frame:preprocess(p._lead(pagename, paralist)) end return p thm9pqarlj02qqozfht2dj6f39j0i5j 797179 797178 2018-04-25T22:25:18Z en>Certes 0 Add support for files= to show selected images from the source 797179 Scribunto text/plain local p = {} -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagename, options) if not pagename then return "" end -- Return blank text rather than an error splurge local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return "" end local text = title:getContent() or "" text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits repeat local oldtext=text text = mw.ustring.gsub(text,"^%A-%b{}%s*","") -- remove infoboxes, hatnotes, tags, etc. from the front until text == oldtext local paragraphlist = options.paragraphs or {} if #paragraphlist > 0 then -- limit to requested paragraphs e.g. {1, 3, 4, 5} local paras = mw.text.split(text, "\n%s*\n") -- %s* may include \n if three or more appear together local sep="" -- no separator before first paragraph local newtext = "" for _, p in pairs(paragraphlist) do if paras[p] then newtext = newtext .. sep .. paras[p] end -- else p exceeds number of paragraphs found sep = "\n\n" end text = newtext end for _, t in pairs {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]", "[Ss]fn[bp]", "[Ss]fb"} do -- remove refs and footnotes text = mw.ustring.gsub(text, "{{%s*" .. t .. "%s*|.-}}", "") end text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs local filelist = options.files or {} local keepfile = {} -- keepfile[n] is true if we want to keep the nth image for _, v in pairs(filelist) do keepfile[v] = true end text = mw.ustring.gsub(text, "%[%[%s*[Ii]mage%s*:", "[[File:") -- now we can ignore Image: local n = 1 -- image count local text2 = "" for t, f in mw.ustring.gmatch(text.."\n[[File:#DUMMY#]]", "(.-)(%[%[%s*[Ff]ile%s*:%C*%]%])") do -- split around files text2 = text2 .. t -- always keep the non-file text if keepfile[n] then text2 = text2 .. f end -- only keep file text if we want this image n = n + 1 end text = mw.ustring.gsub(text2, "^%s*", "") -- remove initial white space text = mw.ustring.gsub(text, "\n%[%[File:#DUMMY#%]%]$", "") -- remove dummy image return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of numbers, e.g. "1,3-5" → {1,3,4,5} function p.numberlist(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local nlist = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do table.insert(nlist, p) end end end return nlist end -- Entry point for template callers using #invoke: function p.lead(frame) -- args = { 1 = page name, paragraphs = list e.g. "1,3-5" } local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagename = args[1] or pargs[1] or "" pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" local paralist = p.numberlist(args["paragraphs"] or pargs["paragraphs"] or "", ",") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} local filelist = p.numberlist(args["files"] or pargs["files"] or "", ",") -- parse file numbers return frame:preprocess(p._lead(pagename, {paragraphs = paralist, files = filelist})) end return p 676w185wz7g2y0ikiosoae7qbdey6nd 797180 797179 2018-04-26T11:49:07Z en>Certes 0 Remove trailing line feeds, so "{{Transclude text excerpt|Foo}} other text" flows onto the same line 797180 Scribunto text/plain local p = {} -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagename, options) if not pagename then return "" end -- Return blank text rather than an error splurge local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return "" end local text = title:getContent() or "" text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits repeat local oldtext=text text = mw.ustring.gsub(text,"^%A-%b{}%s*","") -- remove infoboxes, hatnotes, tags, etc. from the front until text == oldtext local paragraphlist = options.paragraphs or {} if #paragraphlist > 0 then -- limit to requested paragraphs e.g. {1, 3, 4, 5} local paras = mw.text.split(text, "\n%s*\n") -- %s* may include \n if three or more appear together local sep="" -- no separator before first paragraph local newtext = "" for _, p in pairs(paragraphlist) do if paras[p] then newtext = newtext .. sep .. paras[p] end -- else p exceeds number of paragraphs found sep = "\n\n" end text = newtext end for _, t in pairs {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]", "[Ss]fn[bp]", "[Ss]fb"} do -- remove refs and footnotes text = mw.ustring.gsub(text, "{{%s*" .. t .. "%s*|.-}}", "") end text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs local filelist = options.files or {} local keepfile = {} -- keepfile[n] is true if we want to keep the nth image for _, v in pairs(filelist) do keepfile[v] = true end text = mw.ustring.gsub(text, "%[%[%s*[Ii]mage%s*:", "[[File:") -- now we can ignore Image: local n = 1 -- image count local text2 = "" for t, f in mw.ustring.gmatch(text.."\n[[File:#DUMMY#]]", "(.-)(%[%[%s*[Ff]ile%s*:%C*%]%])") do -- split around files text2 = text2 .. t -- always keep the non-file text if keepfile[n] then text2 = text2 .. f end -- only keep file text if we want this image n = n + 1 end text = mw.ustring.gsub(text2, "^%s*", "") -- remove initial white space text = mw.ustring.gsub(text, "\n%[%[File:#DUMMY#%]%]$", "") -- remove dummy image text = mw.ustring.gsub(text, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of numbers, e.g. "1,3-5" → {1,3,4,5} function p.numberlist(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local nlist = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do table.insert(nlist, p) end end end return nlist end -- Entry point for template callers using #invoke: function p.lead(frame) -- args = { 1 = page name, paragraphs = list e.g. "1,3-5" } local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagename = args[1] or pargs[1] or "" pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" local paralist = p.numberlist(args["paragraphs"] or pargs["paragraphs"] or "", ",") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} local filelist = p.numberlist(args["files"] or pargs["files"] or "", ",") -- parse file numbers return frame:preprocess(p._lead(pagename, {paragraphs = paralist, files = filelist})) end return p 9m5ibnd7xf67y2vhpasl63bf9xpjpvz 797181 797180 2018-04-26T16:58:30Z en>Certes 0 Remove HTML comments from between initial templates. Remove refs earlier to cope with incomplete citation templates nested in infoboxes. 797181 Scribunto text/plain local p = {} -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagename, options) if not pagename then return "" end -- Return blank text rather than an error splurge local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return "" end local text = title:getContent() or "" text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs repeat -- remove initial HTML comments and templates such as hatnotes local oldtext=text text = mw.ustring.gsub(text,"^%s*<!%-%-.-%-%->%s*","") -- remove HTML comment from front text = mw.ustring.gsub(text,"^%A-%b{}%s*","") -- remove infobox, hatnote, tag, etc. from front until text == oldtext local paragraphlist = options.paragraphs or {} if #paragraphlist > 0 then -- limit to requested paragraphs e.g. {1, 3, 4, 5} local paras = mw.text.split(text, "\n%s*\n") -- %s* may include \n if three or more appear together local sep="" -- no separator before first paragraph local newtext = "" for _, p in pairs(paragraphlist) do if paras[p] then newtext = newtext .. sep .. paras[p] end -- else p exceeds number of paragraphs found sep = "\n\n" end text = newtext end for _, t in pairs {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]", "[Ss]fn[bp]", "[Ss]fb"} do -- remove refs and footnotes text = mw.ustring.gsub(text, "{{%s*" .. t .. "%s*|.-}}", "") end local filelist = options.files or {} local keepfile = {} -- keepfile[n] is true if we want to keep the nth image for _, v in pairs(filelist) do keepfile[v] = true end text = mw.ustring.gsub(text, "%[%[%s*[Ii]mage%s*:", "[[File:") -- now we can ignore Image: local n = 1 -- image count local text2 = "" for t, f in mw.ustring.gmatch(text.."\n[[File:#DUMMY#]]", "(.-)(%[%[%s*[Ff]ile%s*:%C*%]%])") do -- split around files text2 = text2 .. t -- always keep the non-file text if keepfile[n] then text2 = text2 .. f end -- only keep file text if we want this image n = n + 1 end text = mw.ustring.gsub(text2, "^%s*", "") -- remove initial white space text = mw.ustring.gsub(text, "\n%[%[File:#DUMMY#%]%]$", "") -- remove dummy image text = mw.ustring.gsub(text, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of numbers, e.g. "1,3-5" → {1,3,4,5} function p.numberlist(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local nlist = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do table.insert(nlist, p) end end end return nlist end -- Entry point for template callers using #invoke: function p.lead(frame) -- args = { 1 = page name, paragraphs = list e.g. "1,3-5" } local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagename = args[1] or pargs[1] or "" pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" local paralist = p.numberlist(args["paragraphs"] or pargs["paragraphs"] or "", ",") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} local filelist = p.numberlist(args["files"] or pargs["files"] or "", ",") -- parse file numbers return frame:preprocess(p._lead(pagename, {paragraphs = paralist, files = filelist})) end return p 7zex7tlm96m1pch90ih89kir58kkpmw 797182 797181 2018-04-26T17:14:05Z en>Certes 0 Remove trailing templates such as toc 797182 Scribunto text/plain local p = {} -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagename, options) if not pagename then return "" end -- Return blank text rather than an error splurge local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return "" end local text = title:getContent() or "" text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs repeat -- remove initial HTML comments and templates such as hatnotes local oldtext=text text = mw.ustring.gsub(text,"^%s*<!%-%-.-%-%->%s*","") -- remove HTML comment from front text = mw.ustring.gsub(text,"^%A-%b{}%s*","") -- remove infobox, hatnote, tag, etc. from front until text == oldtext repeat -- remove final HTML comments and templates such as table of contents local oldtext=text text = mw.ustring.gsub(text,"%s*<!%-%-.-%-%->%s*$","") -- remove HTML comment from back text = mw.ustring.gsub(text,"%s*%b{}%s*$","") -- remove toc, etc. from back until text == oldtext local paragraphlist = options.paragraphs or {} if #paragraphlist > 0 then -- limit to requested paragraphs e.g. {1, 3, 4, 5} local paras = mw.text.split(text, "\n%s*\n") -- %s* may include \n if three or more appear together local sep="" -- no separator before first paragraph local newtext = "" for _, p in pairs(paragraphlist) do if paras[p] then newtext = newtext .. sep .. paras[p] end -- else p exceeds number of paragraphs found sep = "\n\n" end text = newtext end for _, t in pairs {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]", "[Ss]fn[bp]", "[Ss]fb"} do -- remove refs and footnotes text = mw.ustring.gsub(text, "{{%s*" .. t .. "%s*|.-}}", "") end local filelist = options.files or {} local keepfile = {} -- keepfile[n] is true if we want to keep the nth image for _, v in pairs(filelist) do keepfile[v] = true end text = mw.ustring.gsub(text, "%[%[%s*[Ii]mage%s*:", "[[File:") -- now we can ignore Image: local n = 1 -- image count local text2 = "" for t, f in mw.ustring.gmatch(text.."\n[[File:#DUMMY#]]", "(.-)(%[%[%s*[Ff]ile%s*:%C*%]%])") do -- split around files text2 = text2 .. t -- always keep the non-file text if keepfile[n] then text2 = text2 .. f end -- only keep file text if we want this image n = n + 1 end text = mw.ustring.gsub(text2, "^%s*", "") -- remove initial white space text = mw.ustring.gsub(text, "\n%[%[File:#DUMMY#%]%]$", "") -- remove dummy image text = mw.ustring.gsub(text, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of numbers, e.g. "1,3-5" → {1,3,4,5} function p.numberlist(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local nlist = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do table.insert(nlist, p) end end end return nlist end -- Entry point for template callers using #invoke: function p.lead(frame) -- args = { 1 = page name, paragraphs = list e.g. "1,3-5" } local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagename = args[1] or pargs[1] or "" pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" local paralist = p.numberlist(args["paragraphs"] or pargs["paragraphs"] or "", ",") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} local filelist = p.numberlist(args["files"] or pargs["files"] or "", ",") -- parse file numbers return frame:preprocess(p._lead(pagename, {paragraphs = paralist, files = filelist})) end return p pf087lbcvbt95uzhjq4kwk4dg11lve7 797183 797182 2018-04-26T23:43:45Z en>Certes 0 Major overhaul to deal with more permutations of templates, comments and images preceding the desired text 797183 Scribunto text/plain local p = {} -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagename, options) if not pagename then return "" end -- Return blank text rather than an error splurge local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return "" end local text = title:getContent() or "" text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs for _, t in pairs {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]", "[Ss]fn[bp]", "[Ss]f[bn]", "NoteTag"} do text = mw.ustring.gsub(text, "{{%s*" .. t .. "%s*|.-}}", "") -- remove ref and footnote templates end text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many [[Image: or [[File: so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a comment, template, image or paragraph local token = mw.ustring.match(text, "^%s*<!%-%-.-%-%->%s*") -- <!--HTML comment--> or mw.ustring.match(text, "^%b{}%s*") or false -- or {{Template}} if token then if inlead then t = t .. token end -- keep comments and templates only within text body else token = mw.ustring.match(text, "^%[%[%s*[Ff]ile%s*:%C*%]%]%s*") -- [[File: ... ]] or mw.ustring.match(text, "^%[%[%s*[Ii]mage%s*:%C*%]%]%s*") -- or [[Image: ... ]] if token then files = files + 1 if options.fileflags and options.fileflags[files] then t = t .. token end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend local endpos = math.min( mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end end end if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end until not text or text == "" or not token or token == "" text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} function p.numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Entry point for template callers using #invoke: function p.lead(frame) -- args = { 1 = page name, paragraphs = list e.g. "1,3-5" } local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagename = args[1] or pargs[1] or "" pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" local paraflags = p.numberflags(args["paragraphs"] or pargs["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} local fileflags = p.numberflags(args["files"] or pargs["files"] or "") -- parse file numbers local text = p._lead(pagename, {paraflags = paraflags, fileflags = fileflags}) return frame:preprocess(text) end return p 3tq1dok6i7db5y0449qjxb0d2plh72w 797184 797183 2018-04-27T18:03:32Z en>Certes 0 Add more= option 797184 Scribunto text/plain local p = {} -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagename, options) if not pagename then return "" end -- Return blank text rather than an error splurge local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return "" end local text = title:getContent() or "" text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs for _, t in pairs {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]", "[Ss]fn[bp]", "[Ss]f[bn]", "NoteTag"} do text = mw.ustring.gsub(text, "{{%s*" .. t .. "%s*|.-}}", "") -- remove ref and footnote templates end text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many [[Image: or [[File: so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a comment, template, image or paragraph local token = mw.ustring.match(text, "^%s*<!%-%-.-%-%->%s*") -- <!--HTML comment--> or mw.ustring.match(text, "^%b{}%s*") or false -- or {{Template}} if token then if inlead then t = t .. token end -- keep comments and templates only within text body else token = mw.ustring.match(text, "^%[%[%s*[Ff]ile%s*:%C*%]%]%s*") -- [[File: ... ]] or mw.ustring.match(text, "^%[%[%s*[Ii]mage%s*:%C*%]%]%s*") -- or [[Image: ... ]] if token then files = files + 1 if options.fileflags and options.fileflags[files] then t = t .. token end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend local endpos = math.min( mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end end end if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end until not text or text == "" or not token or token == "" text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} function p.numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Entry point for template callers using #invoke: function p.lead(frame) -- args = { 1 = page name, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagename = args[1] or pargs[1] or "" pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" local options = {} options.paraflags = p.numberflags(args["paragraphs"] or pargs["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = p.numberflags(args["files"] or pargs["files"] or "") -- parse file numbers options.more = args["more"] or pargs["more"] if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = p._lead(pagename, options) return frame:preprocess(text) end return p dz1e674jxbaa3b0192zg8v8uwt3m8pe 797185 797184 2018-04-27T18:46:57Z en>Certes 0 Handle redirects by transcluding the target 797185 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagename, options) if not pagename then return "" end -- Return blank text rather than an error splurge local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return "" end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local text = title:getContent() or "" text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs for _, t in pairs {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]", "[Ss]fn[bp]", "[Ss]f[bn]", "NoteTag"} do text = mw.ustring.gsub(text, "{{%s*" .. t .. "%s*|.-}}", "") -- remove ref and footnote templates end text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many [[Image: or [[File: so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a comment, template, image or paragraph local token = mw.ustring.match(text, "^%s*<!%-%-.-%-%->%s*") -- <!--HTML comment--> or mw.ustring.match(text, "^%b{}%s*") or false -- or {{Template}} if token then if inlead then t = t .. token end -- keep comments and templates only within text body else token = mw.ustring.match(text, "^%[%[%s*[Ff]ile%s*:%C*%]%]%s*") -- [[File: ... ]] or mw.ustring.match(text, "^%[%[%s*[Ii]mage%s*:%C*%]%]%s*") -- or [[Image: ... ]] if token then files = files + 1 if options.fileflags and options.fileflags[files] then t = t .. token end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend local endpos = math.min( mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end end end if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end until not text or text == "" or not token or token == "" text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} function p.numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Entry point for template callers using #invoke: function p.lead(frame) -- args = { 1 = page name, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagename = args[1] or pargs[1] or "" pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" local options = {} options.paraflags = p.numberflags(args["paragraphs"] or pargs["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = p.numberflags(args["files"] or pargs["files"] or "") -- parse file numbers options.more = args["more"] or pargs["more"] if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = p._lead(pagename, options) return frame:preprocess(text) end return p idpoxkrov8t7vaaxt0tqh7ied5tbc8i 797186 797185 2018-04-28T13:22:27Z en>Certes 0 Bug fix: properly match [[File: and [[Image: which flow over multiple lines or occupy only part of a line 797186 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagename, options) if not pagename then return "" end -- Return blank text rather than an error splurge local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return "" end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local text = title:getContent() or "" text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs for _, t in pairs {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]", "[Ss]fn[bp]", "[Ss]f[bn]", "NoteTag"} do text = mw.ustring.gsub(text, "{{%s*" .. t .. "%s*|.-}}", "") -- remove ref and footnote templates end text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many [[Image: or [[File: so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a comment, template, image or paragraph local token = mw.ustring.match(text, "^%s*<!%-%-.-%-%->%s*") -- <!--HTML comment--> or mw.ustring.match(text, "^%b{}%s*") or false -- or {{Template}} if token then if inlead then t = t .. token end -- keep comments and templates only within text body else token = mw.ustring.match(text, "^%[%[%s*[Ff]ile%s*:") -- [[File: ... or mw.ustring.match(text, "^%[%[%s*[Ii]mage%s*:") -- or [[Image: ... if token then token = mw.ustring.match(text, "^%b[]%s*") -- match [[...]] to handle nesting files = files + 1 if options.fileflags and options.fileflags[files] then t = t .. token end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend local endpos = math.min( mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end end end if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end until not text or text == "" or not token or token == "" text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} function p.numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Entry point for template callers using #invoke: function p.lead(frame) -- args = { 1 = page name, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagename = args[1] or pargs[1] or "" pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" local options = {} options.paraflags = p.numberflags(args["paragraphs"] or pargs["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = p.numberflags(args["files"] or pargs["files"] or "") -- parse file numbers options.more = args["more"] or pargs["more"] if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = p._lead(pagename, options) return frame:preprocess(text) end return p pn0sfr37m9njqsdj012ltb8ndxyz4a1 797187 797186 2018-04-28T14:12:11Z en>Certes 0 Adding random article feature 797187 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagenames, options) if not pagenames or #pagenames < 1 then return "" end -- Return blank text rather than an error splurge local pagename = pagenames[1] if #pagenames > 1 then -- we could do this even with one page, but it would be inefficient math.randomseed(os.time()) pagename = pagenames[math.random(#pagenames)] --pick a random title end pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return "" end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local text = title:getContent() or "" text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs for _, t in pairs {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]", "[Ss]fn[bp]", "[Ss]f[bn]", "NoteTag"} do text = mw.ustring.gsub(text, "{{%s*" .. t .. "%s*|.-}}", "") -- remove ref and footnote templates end text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many [[Image: or [[File: so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a comment, template, image or paragraph local token = mw.ustring.match(text, "^%s*<!%-%-.-%-%->%s*") -- <!--HTML comment--> or mw.ustring.match(text, "^%b{}%s*") or false -- or {{Template}} if token then if inlead then t = t .. token end -- keep comments and templates only within text body else token = mw.ustring.match(text, "^%[%[%s*[Ff]ile%s*:") -- [[File: ... or mw.ustring.match(text, "^%[%[%s*[Ii]mage%s*:") -- or [[Image: ... if token then token = mw.ustring.match(text, "^%b[]%s*") -- match [[...]] to handle nesting files = files + 1 if options.fileflags and options.fileflags[files] then t = t .. token end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend local endpos = math.min( mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end end end if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end until not text or text == "" or not token or token == "" text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} function p.numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Entry point for template callers using #invoke: function p.lead(frame) -- args = { 1 = page name, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template -- Accept any number of page names. If more than one, we'll pick one randomly local pagenames = {} for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end for i, p in pairs(pargs) do if p and type(i) == 'number' and not args[i] then table.insert(pagenames, p) end end local options = {} options.paraflags = p.numberflags(args["paragraphs"] or pargs["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = p.numberflags(args["files"] or pargs["files"] or "") -- parse file numbers options.more = args["more"] or pargs["more"] if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = p._lead(pagenames, options) return frame:preprocess(text) end -- Function lead is just function random picking from a list of one article, -- but we advertise different entry points in case they should differ in future function p.random(frame) return p.lead(frame) end return p 025m8kspwvj2egfuc1rcyb3f7t5v1ho 797188 797187 2018-04-28T14:32:32Z en>Certes 0 Strip leading and trailing white space from article names 797188 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagenames, options) if not pagenames or #pagenames < 1 then return "" end -- Return blank text rather than an error splurge local pagename = pagenames[1] if #pagenames > 1 then -- we could do this even with one page, but it would be inefficient math.randomseed(os.time()) pagename = pagenames[math.random(#pagenames)] --pick a random title end pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.match(pagename, "%S.*%S") -- strip leading and trailing white space local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return "" end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local text = title:getContent() or "" text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs for _, t in pairs {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]", "[Ss]fn[bp]", "[Ss]f[bn]", "NoteTag"} do text = mw.ustring.gsub(text, "{{%s*" .. t .. "%s*|.-}}", "") -- remove ref and footnote templates end text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many [[Image: or [[File: so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a comment, template, image or paragraph local token = mw.ustring.match(text, "^%s*<!%-%-.-%-%->%s*") -- <!--HTML comment--> or mw.ustring.match(text, "^%b{}%s*") or false -- or {{Template}} if token then if inlead then t = t .. token end -- keep comments and templates only within text body else token = mw.ustring.match(text, "^%[%[%s*[Ff]ile%s*:") -- [[File: ... or mw.ustring.match(text, "^%[%[%s*[Ii]mage%s*:") -- or [[Image: ... if token then token = mw.ustring.match(text, "^%b[]%s*") -- match [[...]] to handle nesting files = files + 1 if options.fileflags and options.fileflags[files] then t = t .. token end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend local endpos = math.min( mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end end end if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end until not text or text == "" or not token or token == "" text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} function p.numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Entry point for template callers using #invoke: function p.lead(frame) -- args = { 1 = page name, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template -- Accept any number of page names. If more than one, we'll pick one randomly local pagenames = {} for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end for i, p in pairs(pargs) do if p and type(i) == 'number' and not args[i] then table.insert(pagenames, p) end end local options = {} options.paraflags = p.numberflags(args["paragraphs"] or pargs["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = p.numberflags(args["files"] or pargs["files"] or "") -- parse file numbers options.more = args["more"] or pargs["more"] if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = p._lead(pagenames, options) return frame:preprocess(text) end -- Function lead is just function random picking from a list of one article, -- but we advertise different entry points in case they should differ in future function p.random(frame) return p.lead(frame) end return p 19j92ulml1g5bcbrmc2l5xoi6zimhot 797189 797188 2018-04-28T14:45:08Z en>Certes 0 Removing {{#tag:ref ... }} 797189 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagenames, options) if not pagenames or #pagenames < 1 then return "" end -- Return blank text rather than an error splurge local pagename = pagenames[1] if #pagenames > 1 then -- we could do this even with one page, but it would be inefficient math.randomseed(os.time()) pagename = pagenames[math.random(#pagenames)] --pick a random title end pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.match(pagename, "%S.*%S") -- strip leading and trailing white space local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return "" end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local text = title:getContent() or "" text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs for _, t in pairs {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]", "[Ss]fn[bp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef"} do text = mw.ustring.gsub(text, "{{%s*" .. t .. "%s*|.-}}", "") -- remove ref and footnote templates end text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many [[Image: or [[File: so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a comment, template, image or paragraph local token = mw.ustring.match(text, "^%s*<!%-%-.-%-%->%s*") -- <!--HTML comment--> or mw.ustring.match(text, "^%b{}%s*") or false -- or {{Template}} if token then if inlead then t = t .. token end -- keep comments and templates only within text body else token = mw.ustring.match(text, "^%[%[%s*[Ff]ile%s*:") -- [[File: ... or mw.ustring.match(text, "^%[%[%s*[Ii]mage%s*:") -- or [[Image: ... if token then token = mw.ustring.match(text, "^%b[]%s*") -- match [[...]] to handle nesting files = files + 1 if options.fileflags and options.fileflags[files] then t = t .. token end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend local endpos = math.min( mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end end end if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end until not text or text == "" or not token or token == "" text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} function p.numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Entry point for template callers using #invoke: function p.lead(frame) -- args = { 1 = page name, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template -- Accept any number of page names. If more than one, we'll pick one randomly local pagenames = {} for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end for i, p in pairs(pargs) do if p and type(i) == 'number' and not args[i] then table.insert(pagenames, p) end end local options = {} options.paraflags = p.numberflags(args["paragraphs"] or pargs["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = p.numberflags(args["files"] or pargs["files"] or "") -- parse file numbers options.more = args["more"] or pargs["more"] if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = p._lead(pagenames, options) return frame:preprocess(text) end -- Function lead is just function random picking from a list of one article, -- but we advertise different entry points in case they should differ in future function p.random(frame) return p.lead(frame) end return p rw4kuv6jj0jklb2gjiyyuh8mbui0f61 797190 797189 2018-05-03T13:55:31Z en>Certes 0 For lead, ignore all but the first unnamed argument 797190 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagenames, options) if not pagenames or #pagenames < 1 then return "" end -- Return blank text rather than an error splurge local pagename = pagenames[1] if #pagenames > 1 then -- we could do this even with one page, but it would be inefficient math.randomseed(os.time()) pagename = pagenames[math.random(#pagenames)] --pick a random title end pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.match(pagename, "%S.*%S") -- strip leading and trailing white space local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return "" end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local text = title:getContent() or "" text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs for _, t in pairs {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]", "[Ss]fn[bp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef"} do text = mw.ustring.gsub(text, "{{%s*" .. t .. "%s*|.-}}", "") -- remove ref and footnote templates end text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many [[Image: or [[File: so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a comment, template, image or paragraph local token = mw.ustring.match(text, "^%s*<!%-%-.-%-%->%s*") -- <!--HTML comment--> or mw.ustring.match(text, "^%b{}%s*") or false -- or {{Template}} if token then if inlead then t = t .. token end -- keep comments and templates only within text body else token = mw.ustring.match(text, "^%[%[%s*[Ff]ile%s*:") -- [[File: ... or mw.ustring.match(text, "^%[%[%s*[Ii]mage%s*:") -- or [[Image: ... if token then token = mw.ustring.match(text, "^%b[]%s*") -- match [[...]] to handle nesting files = files + 1 if options.fileflags and options.fileflags[files] then t = t .. token end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend local endpos = math.min( mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end end end if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end until not text or text == "" or not token or token == "" text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} function p.numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions function p.leadrandom(frame, israndom) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagenames = { args[1] or pargs[1] } -- For lead, ignore all but the first unnamed argument if israndom then -- For random, accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' and i > 1 then table.insert(pagenames, p) end end for i, p in pairs(pargs) do if p and type(i) == 'number' and i > 1 and not args[i] then table.insert(pagenames, p) end end end local options = {} options.paraflags = p.numberflags(args["paragraphs"] or pargs["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = p.numberflags(args["files"] or pargs["files"] or "") -- parse file numbers options.more = args["more"] or pargs["more"] if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = p._lead(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return p.leadrandom(frame, false) end function p.random(frame) return p.leadrandom(frame, true) end return p ofmqba7vqxuwcoxycr1agu90cksbflx 797191 797190 2018-05-03T15:30:00Z en>Certes 0 Improve error handling 797191 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagenames, options) errors = options.errors --if 1 then return "options.errors=("..options.errors..") errors=("..errors..")" end if not pagenames or #pagenames < 1 then return p.err("No page names given") end local pagename = pagenames[1] if #pagenames > 1 then -- we could do this even with one page, but it would be inefficient math.randomseed(os.time()) pagename = pagenames[math.random(#pagenames)] --pick a random title end pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.match(pagename, "%S.*%S") -- strip leading and trailing white space if not pagename then return p.err("Nil page name") end if pagename == "" then return p.err("Blank page name") end local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return p.err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local text = title:getContent() if not text then return p.err("No page for name " .. pagename) end text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs for _, t in pairs {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]", "[Ss]fn[bp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef"} do text = mw.ustring.gsub(text, "{{%s*" .. t .. "%s*|.-}}", "") -- remove ref and footnote templates end text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many [[Image: or [[File: so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a comment, template, image or paragraph local token = mw.ustring.match(text, "^%s*<!%-%-.-%-%->%s*") -- <!--HTML comment--> or mw.ustring.match(text, "^%b{}%s*") or false -- or {{Template}} if token then if inlead then t = t .. token end -- keep comments and templates only within text body else token = mw.ustring.match(text, "^%[%[%s*[Ff]ile%s*:") -- [[File: ... or mw.ustring.match(text, "^%[%[%s*[Ii]mage%s*:") -- or [[Image: ... if token then token = mw.ustring.match(text, "^%b[]%s*") -- match [[...]] to handle nesting files = files + 1 if options.fileflags and options.fileflags[files] then t = t .. token end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend local endpos = math.min( mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end end end if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end until not text or text == "" or not token or token == "" text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end return text end -- Return blank text, or an error message if requested function p.err(text, options) if errors then error(text, 2) end return "" end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} function p.numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions function p.leadrandom(frame, israndom) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagenames = { args[1] or pargs[1] } -- For lead, ignore all but the first unnamed argument if israndom then -- For random, accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' and i > 1 then table.insert(pagenames, p) end end for i, p in pairs(pargs) do if p and type(i) == 'number' and i > 1 and not args[i] then table.insert(pagenames, p) end end end local options = {} options.paraflags = p.numberflags(args["paragraphs"] or pargs["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = p.numberflags(args["files"] or pargs["files"] or "") -- parse file numbers options.more = args["more"] or pargs["more"] if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text options.errors = args["errors"] or pargs["errors"] local text = p._lead(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return p.leadrandom(frame, false) end function p.random(frame) return p.leadrandom(frame, true) end return p gmg25fhqwbirdivt9aaemydwe1dplbr 797192 797191 2018-05-03T16:26:58Z en>Certes 0 If the random page is unavailable, bring on a substitute 797192 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagenames, options) errors = options.errors if not pagenames or #pagenames < 1 then return p.err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.match(pagename, "%S.*%S") -- strip leading and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return p.err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop eventually end if not text then return p.err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs for _, t in pairs {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]", "[Ss]fn[bp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef"} do text = mw.ustring.gsub(text, "{{%s*" .. t .. "%s*|.-}}", "") -- remove ref and footnote templates end text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many [[Image: or [[File: so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a comment, template, image or paragraph local token = mw.ustring.match(text, "^%s*<!%-%-.-%-%->%s*") -- <!--HTML comment--> or mw.ustring.match(text, "^%b{}%s*") or false -- or {{Template}} if token then if inlead then t = t .. token end -- keep comments and templates only within text body else token = mw.ustring.match(text, "^%[%[%s*[Ff]ile%s*:") -- [[File: ... or mw.ustring.match(text, "^%[%[%s*[Ii]mage%s*:") -- or [[Image: ... if token then token = mw.ustring.match(text, "^%b[]%s*") -- match [[...]] to handle nesting files = files + 1 if options.fileflags and options.fileflags[files] then t = t .. token end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend local endpos = math.min( mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end end end if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end until not text or text == "" or not token or token == "" text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end return text end -- Return blank text, or an error message if requested function p.err(text, options) if errors then error(text, 2) end return "" end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} function p.numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions function p.leadrandom(frame, israndom) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagenames = { args[1] or pargs[1] } -- For lead, ignore all but the first unnamed argument if israndom then -- For random, accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' and i > 1 then table.insert(pagenames, p) end end for i, p in pairs(pargs) do if p and type(i) == 'number' and i > 1 and not args[i] then table.insert(pagenames, p) end end end local options = {} options.paraflags = p.numberflags(args["paragraphs"] or pargs["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = p.numberflags(args["files"] or pargs["files"] or "") -- parse file numbers options.more = args["more"] or pargs["more"] if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text options.errors = args["errors"] or pargs["errors"] local text = p._lead(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return p.leadrandom(frame, false) end function p.random(frame) return p.leadrandom(frame, true) end return p kri78n05fcd2yidh0j3t3gie3buox1m 797193 797192 2018-05-06T18:48:03Z en>Certes 0 Also remove [[Template:Rp]] 797193 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagenames, options) errors = options.errors if not pagenames or #pagenames < 1 then return p.err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.match(pagename, "%S.*%S") -- strip leading and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return p.err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop eventually end if not text then return p.err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs for _, t in pairs {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef"} do text = mw.ustring.gsub(text, "{{%s*" .. t .. "%s*|.-}}", "") -- remove ref and footnote templates end text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many [[Image: or [[File: so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a comment, template, image or paragraph local token = mw.ustring.match(text, "^%s*<!%-%-.-%-%->%s*") -- <!--HTML comment--> or mw.ustring.match(text, "^%b{}%s*") or false -- or {{Template}} if token then if inlead then t = t .. token end -- keep comments and templates only within text body else token = mw.ustring.match(text, "^%[%[%s*[Ff]ile%s*:") -- [[File: ... or mw.ustring.match(text, "^%[%[%s*[Ii]mage%s*:") -- or [[Image: ... if token then token = mw.ustring.match(text, "^%b[]%s*") -- match [[...]] to handle nesting files = files + 1 if options.fileflags and options.fileflags[files] then t = t .. token end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend local endpos = math.min( mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end end end if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end until not text or text == "" or not token or token == "" text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end return text end -- Return blank text, or an error message if requested function p.err(text, options) if errors then error(text, 2) end return "" end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} function p.numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions function p.leadrandom(frame, israndom) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagenames = { args[1] or pargs[1] } -- For lead, ignore all but the first unnamed argument if israndom then -- For random, accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' and i > 1 then table.insert(pagenames, p) end end for i, p in pairs(pargs) do if p and type(i) == 'number' and i > 1 and not args[i] then table.insert(pagenames, p) end end end local options = {} options.paraflags = p.numberflags(args["paragraphs"] or pargs["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = p.numberflags(args["files"] or pargs["files"] or "") -- parse file numbers options.more = args["more"] or pargs["more"] if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text options.errors = args["errors"] or pargs["errors"] local text = p._lead(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return p.leadrandom(frame, false) end function p.random(frame) return p.leadrandom(frame, true) end return p 82nif1c8w0lzrkpsujs85ihldkdkbdp 797194 797193 2018-05-09T00:26:54Z en>Certes 0 Remove imagemap 797194 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagenames, options) errors = options.errors if not pagenames or #pagenames < 1 then return p.err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.match(pagename, "%S.*%S") -- strip leading and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return p.err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop eventually end if not text then return p.err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps for _, t in pairs {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef"} do text = mw.ustring.gsub(text, "{{%s*" .. t .. "%s*|.-}}", "") -- remove ref and footnote templates end text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many [[Image: or [[File: so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a comment, template, image or paragraph local token = mw.ustring.match(text, "^%s*<!%-%-.-%-%->%s*") -- <!--HTML comment--> or mw.ustring.match(text, "^%b{}%s*") or false -- or {{Template}} if token then if inlead then t = t .. token end -- keep comments and templates only within text body else token = mw.ustring.match(text, "^%[%[%s*[Ff]ile%s*:") -- [[File: ... or mw.ustring.match(text, "^%[%[%s*[Ii]mage%s*:") -- or [[Image: ... if token then token = mw.ustring.match(text, "^%b[]%s*") -- match [[...]] to handle nesting files = files + 1 if options.fileflags and options.fileflags[files] then t = t .. token end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend local endpos = math.min( mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end end end if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end until not text or text == "" or not token or token == "" text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end return text end -- Return blank text, or an error message if requested function p.err(text, options) if errors then error(text, 2) end return "" end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} function p.numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions function p.leadrandom(frame, israndom) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagenames = { args[1] or pargs[1] } -- For lead, ignore all but the first unnamed argument if israndom then -- For random, accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' and i > 1 then table.insert(pagenames, p) end end for i, p in pairs(pargs) do if p and type(i) == 'number' and i > 1 and not args[i] then table.insert(pagenames, p) end end end local options = {} options.paraflags = p.numberflags(args["paragraphs"] or pargs["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = p.numberflags(args["files"] or pargs["files"] or "") -- parse file numbers options.more = args["more"] or pargs["more"] if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text options.errors = args["errors"] or pargs["errors"] local text = p._lead(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return p.leadrandom(frame, false) end function p.random(frame) return p.leadrandom(frame, true) end return p e60uqlijk1fxc8dtpkaib2texkr87wh 797195 797194 2018-05-09T18:41:11Z en>Certes 0 Parse "File:" within templates within preamble (testcase: Man Utd). Strip citation/disambiguation needed templates. Declare functions as local. 797195 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere or at the start local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = mw.ustring.match(text, startre .. "%[%[%s*[Ff]ile%s*:.*") -- [[File: ... or mw.ustring.match(text, startre .. "%[%[%s*[Ii]mage%s*:.*") -- or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- match [[...]] to handle nesting end return image end -- Return blank text, or an error message if requested local function err(text, options) if errors then error(text, 2) end return "" end -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagenames, options) errors = options.errors if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.match(pagename, "%S.*%S") -- strip leading and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop eventually end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps for _, t in pairs {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[CcDd]n", "Citation needed", "Disambiguation needed"} do text = mw.ustring.gsub(text, "{{%s*" .. t .. "%s*|.-}}", "") -- remove ref and footnote templates end text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many [[Image: or [[File: so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a comment, template, image or paragraph local token = mw.ustring.match(text, "^%s*<!%-%-.-%-%->%s*") -- <!--HTML comment--> or mw.ustring.match(text, "^%b{}%s*") or false -- or {{Template}} if token then if inlead then -- keep comments and templates only within text body t = t .. token else -- look for [[File:... embedded in an infobox etc. in the preamble local image = parseimage(token, false) if image then -- keep comments and templates only within text body image = mw.ustring.gsub(image, "|%s*frameless", "|frame") -- excerpt needs a frame to flow around, even if infobox doesn't files = files + 1 if options.fileflags and options.fileflags[files] then t = t .. image .. "\n" end end end else token = parseimage(text, true) if token then files = files + 1 if options.fileflags and options.fileflags[files] then t = t .. token end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend local endpos = math.min( mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end end end if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end until not text or text == "" or not token or token == "" text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function leadrandom(frame, israndom) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagenames = { args[1] or pargs[1] } -- For lead, ignore all but the first unnamed argument if israndom then -- For random, accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' and i > 1 then table.insert(pagenames, p) end end for i, p in pairs(pargs) do if p and type(i) == 'number' and i > 1 and not args[i] then table.insert(pagenames, p) end end end local options = {} options.paraflags = numberflags(args["paragraphs"] or pargs["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or pargs["files"] or "") -- parse file numbers options.more = args["more"] or pargs["more"] if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text options.errors = args["errors"] or pargs["errors"] local text = p._lead(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return leadrandom(frame, false) end function p.random(frame) return leadrandom(frame, true) end return p 5an2b1lhp3cto4dkw31t3i6m2qjhvwx 797196 797195 2018-05-09T19:38:54Z en>Certes 0 Parse image= etc. within infobox. Remove HTML comments earlier in case they lurk within infoboxes. 797196 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere or at the start local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = mw.ustring.match(text, startre .. "%[%[%s*[Ff]ile%s*:.*") -- [[File: ... or mw.ustring.match(text, startre .. "%[%[%s*[Ii]mage%s*:.*") -- or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- match [[...]] to handle nesting end return image end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "%{%{%s*[Ii]nfobox") then local image = mw.ustring.match(text, "|%s*image%s*=%s*([^%}|]*)") -- parse image= argument... or mw.ustring.match(text, "|%s*Cover%s*=%s*([^%}|]-)") -- or Cover= from Infobox album if image then -- add in relevant parameters: caption, alt text and image size token = "[[File:" .. image local caption = mw.ustring.match(text, "|%s*[Cc]aption%s*=%s*([^%}|]*)") if caption then token = token .. "|caption=" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^%}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^%}|]*)") if image_size then token = token .. "|" .. image_size end token = mw.ustring.gsub(token, "\n","") .. "|frame]]\n" end end return token end -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagenames, options) errors = options.errors if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.match(pagename, "%S.*%S") -- strip leading and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop eventually end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps for _, t in pairs {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[CcDd]n", "Citation needed", "Disambiguation needed"} do text = mw.ustring.gsub(text, "{{%s*" .. t .. "%s*|.-}}", "") -- remove ref and footnote templates end text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many [[Image: or [[File: so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if token then if inlead then -- keep comments and templates only within text body t = t .. token else -- look for [[File:... embedded in an infobox etc. in the preamble local image = parseimage(token, false) or argimage(token) if image then -- keep comments and templates only within text body image = mw.ustring.gsub(image, "|%s*frameless", "|frame") -- excerpt needs a frame to flow around, even if infobox doesn't files = files + 1 if options.fileflags and options.fileflags[files] then t = t .. image end end end else token = parseimage(text, true) if token then files = files + 1 if options.fileflags and options.fileflags[files] then t = t .. token end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend local endpos = math.min( mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end end end if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end until not text or text == "" or not token or token == "" text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function leadrandom(frame, israndom) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagenames = { args[1] or pargs[1] } -- For lead, ignore all but the first unnamed argument if israndom then -- For random, accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' and i > 1 then table.insert(pagenames, p) end end for i, p in pairs(pargs) do if p and type(i) == 'number' and i > 1 and not args[i] then table.insert(pagenames, p) end end end local options = {} options.paraflags = numberflags(args["paragraphs"] or pargs["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or pargs["files"] or "") -- parse file numbers options.more = args["more"] or pargs["more"] if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text options.errors = args["errors"] or pargs["errors"] local text = p._lead(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return leadrandom(frame, false) end function p.random(frame) return leadrandom(frame, true) end return p b3xzqzmq8lyepv10hpka2ecmzt9wxc8 797197 797196 2018-05-09T20:09:31Z en>Certes 0 Thumb sized images 797197 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere or at the start local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = mw.ustring.match(text, startre .. "%[%[%s*[Ff]ile%s*:.*") -- [[File: ... or mw.ustring.match(text, startre .. "%[%[%s*[Ii]mage%s*:.*") -- or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- match [[...]] to handle nesting end return image end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "%{%{%s*[Ii]nfobox") then local image = mw.ustring.match(text, "|%s*image%s*=%s*([^%}|]*)") -- parse image= argument... or mw.ustring.match(text, "|%s*Cover%s*=%s*([^%}|]-)") -- or Cover= from Infobox album if image then -- add in relevant parameters: caption, alt text and image size token = "[[File:" .. image local caption = mw.ustring.match(text, "|%s*[Cc]aption%s*=%s*([^%}|]*)") if caption then token = token .. "|caption=" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^%}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^%}|]*)") if image_size then token = token .. "|" .. image_size end token = mw.ustring.gsub(token, "\n","") .. "|thumb]]\n" end end return token end -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagenames, options) errors = options.errors if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.match(pagename, "%S.*%S") -- strip leading and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop eventually end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps for _, t in pairs {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[CcDd]n", "Citation needed", "Disambiguation needed"} do text = mw.ustring.gsub(text, "{{%s*" .. t .. "%s*|.-}}", "") -- remove ref and footnote templates end text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many [[Image: or [[File: so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if token then if inlead then -- keep comments and templates only within text body t = t .. token else -- look for [[File:... embedded in an infobox etc. in the preamble local image = parseimage(token, false) or argimage(token) if image then -- keep comments and templates only within text body image = mw.ustring.gsub(image, "|%s*frameless", "|frame") -- excerpt needs a frame to flow around, even if infobox doesn't files = files + 1 if options.fileflags and options.fileflags[files] then t = t .. image end end end else token = parseimage(text, true) if token then files = files + 1 if options.fileflags and options.fileflags[files] then t = t .. token end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend local endpos = math.min( mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end end end if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end until not text or text == "" or not token or token == "" text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function leadrandom(frame, israndom) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagenames = { args[1] or pargs[1] } -- For lead, ignore all but the first unnamed argument if israndom then -- For random, accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' and i > 1 then table.insert(pagenames, p) end end for i, p in pairs(pargs) do if p and type(i) == 'number' and i > 1 and not args[i] then table.insert(pagenames, p) end end end local options = {} options.paraflags = numberflags(args["paragraphs"] or pargs["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or pargs["files"] or "") -- parse file numbers options.more = args["more"] or pargs["more"] if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text options.errors = args["errors"] or pargs["errors"] local text = p._lead(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return leadrandom(frame, false) end function p.random(frame) return leadrandom(frame, true) end return p 9zukxr7vkrm5akxry8sv82mezm3h7bl 797198 797197 2018-05-09T22:47:36Z en>Certes 0 Reduce image obtained from infobox to thumb size for [[File:... syntax too 797198 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere or at the start local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = mw.ustring.match(text, startre .. "%[%[%s*[Ff]ile%s*:.*") -- [[File: ... or mw.ustring.match(text, startre .. "%[%[%s*[Ii]mage%s*:.*") -- or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- match [[...]] to handle nesting end return image end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "%{%{%s*[Ii]nfobox") then local image = mw.ustring.match(text, "|%s*image%s*=%s*([^%}|]*)") -- parse image= argument... or mw.ustring.match(text, "|%s*Cover%s*=%s*([^%}|]-)") -- or Cover= from Infobox album if image then -- add in relevant parameters: caption, alt text and image size token = "[[File:" .. image local caption = mw.ustring.match(text, "|%s*[Cc]aption%s*=%s*([^%}|]*)") if caption then token = token .. "|caption=" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^%}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^%}|]*)") if image_size then token = token .. "|" .. image_size end token = mw.ustring.gsub(token, "\n","") .. "|thumb]]\n" end end return token end -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagenames, options) errors = options.errors if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.match(pagename, "%S.*%S") -- strip leading and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop eventually end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps for _, t in pairs {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[CcDd]n", "Citation needed", "Disambiguation needed"} do text = mw.ustring.gsub(text, "{{%s*" .. t .. "%s*|.-}}", "") -- remove ref and footnote templates end text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many [[Image: or [[File: so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if token then if inlead then -- keep comments and templates only within text body t = t .. token else -- look for [[File:... embedded in an infobox etc. in the preamble local image = parseimage(token, false) or argimage(token) if image then -- keep comments and templates only within text body image = mw.ustring.gsub(image, "|%s*frameless", "|thumb") -- excerpt needs a frame to flow around, even if infobox doesn't files = files + 1 if options.fileflags and options.fileflags[files] then t = t .. image end end end else token = parseimage(text, true) if token then files = files + 1 if options.fileflags and options.fileflags[files] then t = t .. token end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend local endpos = math.min( mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end end end if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end until not text or text == "" or not token or token == "" text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function leadrandom(frame, israndom) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagenames = { args[1] or pargs[1] } -- For lead, ignore all but the first unnamed argument if israndom then -- For random, accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' and i > 1 then table.insert(pagenames, p) end end for i, p in pairs(pargs) do if p and type(i) == 'number' and i > 1 and not args[i] then table.insert(pagenames, p) end end end local options = {} options.paraflags = numberflags(args["paragraphs"] or pargs["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or pargs["files"] or "") -- parse file numbers options.more = args["more"] or pargs["more"] if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text options.errors = args["errors"] or pargs["errors"] local text = p._lead(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return leadrandom(frame, false) end function p.random(frame) return leadrandom(frame, true) end return p qnay21cyw3kpljq4td3nk72g1jpy4o3 797199 797198 2018-05-13T11:42:23Z en>Certes 0 Check for non-free files, including a fix developed in sandbox by [[Evad37]] 797199 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- Check image for suitablity local function checkimage(image) local page = mw.ustring.match(image, "([Ff]ile%s*:[^|%]]*)") -- File:(name) ... or mw.ustring.match(image, "([Ii]mage%s*:([^|%]]*)") -- or Image:(name) ... if not page then return nil end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return nil end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere or at the start local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = mw.ustring.match(text, startre .. "%[%[%s*[Ff]ile%s*:.*") -- [[File: ... or mw.ustring.match(text, startre .. "%[%[%s*[Ii]mage%s*:.*") -- or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- match [[...]] to handle nesting end return image end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "%{%{%s*[Ii]nfobox") then local image = mw.ustring.match(text, "|%s*image%s*=%s*([^%}|]*)") -- parse image= argument... or mw.ustring.match(text, "|%s*Cover%s*=%s*([^%}|]-)") -- or Cover= from Infobox album if image then -- add in relevant parameters: caption, alt text and image size token = "[[File:" .. image local caption = mw.ustring.match(text, "|%s*[Cc]aption%s*=%s*([^%}|]*)") if caption then token = token .. "|caption=" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^%}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^%}|]*)") if image_size then token = token .. "|" .. image_size end token = mw.ustring.gsub(token, "\n","") .. "|thumb]]\n" end end return token end -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagenames, options) errors = options.errors if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.match(pagename, "%S.*%S") -- strip leading and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop eventually end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps for _, t in pairs {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[CcDd]n", "Citation needed", "Disambiguation needed"} do text = mw.ustring.gsub(text, "{{%s*" .. t .. "%s*|.-}}", "") -- remove ref and footnote templates end text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many [[Image: or [[File: so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if token then if inlead then -- keep comments and templates only within text body t = t .. token elseif files < maxfile then -- look for [[File:... embedded in an infobox etc. in the preamble local image = parseimage(token, false) or argimage(token) if image and checkimage(image) then -- keep comments and templates only within text body image = mw.ustring.gsub(image, "|%s*frameless", "|thumb") -- excerpt needs a frame to flow around, even if infobox doesn't files = files + 1 if options.fileflags and options.fileflags[files] then t = t .. image end end end else token = parseimage(text, true) if token then if files < maxfile and checkimage(token) then files = files + 1 if options.fileflags and options.fileflags[files] then t = t .. token end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend local endpos = math.min( mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end end end if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end until not text or text == "" or not token or token == "" text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function leadrandom(frame, israndom) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagenames = { args[1] or pargs[1] } -- For lead, ignore all but the first unnamed argument if israndom then -- For random, accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' and i > 1 then table.insert(pagenames, p) end end for i, p in pairs(pargs) do if p and type(i) == 'number' and i > 1 and not args[i] then table.insert(pagenames, p) end end end local options = {} options.paraflags = numberflags(args["paragraphs"] or pargs["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or pargs["files"] or "") -- parse file numbers options.more = args["more"] or pargs["more"] if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text options.errors = args["errors"] or pargs["errors"] local text = p._lead(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return leadrandom(frame, false) end function p.random(frame) return leadrandom(frame, true) end return p nr4yxqhl7u7prdjqiqfdn5v5b1nd0x4 797200 797199 2018-05-13T12:16:06Z en>Certes 0 Convert infobox image to thumbnail, even if extracted from an enclosed "File:" link. Also fixed a bug with caption= appearing. 797200 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- Check image for suitablity local function checkimage(image) local page = mw.ustring.match(image, "([Ff]ile%s*:[^|%]]*)") -- File:(name) ... or mw.ustring.match(image, "([Ii]mage%s*:([^|%]]*)") -- or Image:(name) ... if not page then return nil end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return nil end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere or at the start local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = mw.ustring.match(text, startre .. "%[%[%s*[Ff]ile%s*:.*") -- [[File: ... or mw.ustring.match(text, startre .. "%[%[%s*[Ii]mage%s*:.*") -- or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- match [[...]] to handle nesting end return image end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "%{%{%s*[Ii]nfobox") then local image = mw.ustring.match(text, "|%s*image%s*=%s*([^%}|]*)") -- parse image= argument... or mw.ustring.match(text, "|%s*Cover%s*=%s*([^%}|]-)") -- or Cover= from Infobox album if image then -- add in relevant parameters: caption, alt text and image size token = "[[File:" .. image local caption = mw.ustring.match(text, "|%s*[Cc]aption%s*=%s*([^%}|]*)") if caption then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^%}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^%}|]*)") if image_size then token = token .. "|" .. image_size end token = mw.ustring.gsub(token, "\n","") .. "]]\n" end end return token end -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagenames, options) errors = options.errors if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.match(pagename, "%S.*%S") -- strip leading and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop eventually end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps for _, t in pairs {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[CcDd]n", "Citation needed", "Disambiguation needed"} do text = mw.ustring.gsub(text, "{{%s*" .. t .. "%s*|.-}}", "") -- remove ref and footnote templates end text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many [[Image: or [[File: so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if token then if inlead then -- keep comments and templates only within text body t = t .. token elseif files < maxfile then -- look for [[File:... embedded in an infobox etc. in the preamble local image = parseimage(token, false) or argimage(token) if image and checkimage(image) then -- keep comments and templates only within text body image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not mw.ustring.match(image, "|%s*thumb%s*%f[|%]]") and not mw.ustring.match(image, "|%s*thumbnail%s*%f[|%]]") then image = mw.ustring.gsub(image, "%]%]%s*$", "|thumb]]") end files = files + 1 if options.fileflags and options.fileflags[files] then t = t .. image end end end else token = parseimage(text, true) if token then if files < maxfile and checkimage(token) then files = files + 1 if options.fileflags and options.fileflags[files] then t = t .. token end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend local endpos = math.min( mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end end end if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end until not text or text == "" or not token or token == "" text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function leadrandom(frame, israndom) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagenames = { args[1] or pargs[1] } -- For lead, ignore all but the first unnamed argument if israndom then -- For random, accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' and i > 1 then table.insert(pagenames, p) end end for i, p in pairs(pargs) do if p and type(i) == 'number' and i > 1 and not args[i] then table.insert(pagenames, p) end end end local options = {} options.paraflags = numberflags(args["paragraphs"] or pargs["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or pargs["files"] or "") -- parse file numbers options.more = args["more"] or pargs["more"] if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text options.errors = args["errors"] or pargs["errors"] local text = p._lead(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return leadrandom(frame, false) end function p.random(frame) return leadrandom(frame, true) end return p owzrzuncpvpwd2juzehr3y9mlc5h5hm 797201 797200 2018-05-13T12:31:27Z en>Certes 0 Adding fileargs= to allow images on the left. Minor efficiency improvement (skip text processing on File:... if this image not wanted) 797201 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- Check image for suitablity local function checkimage(image) local page = mw.ustring.match(image, "([Ff]ile%s*:[^|%]]*)") -- File:(name) ... or mw.ustring.match(image, "([Ii]mage%s*:([^|%]]*)") -- or Image:(name) ... if not page then return nil end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return nil end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere or at the start local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = mw.ustring.match(text, startre .. "%[%[%s*[Ff]ile%s*:.*") -- [[File: ... or mw.ustring.match(text, startre .. "%[%[%s*[Ii]mage%s*:.*") -- or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- match [[...]] to handle nesting end return image end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "%{%{%s*[Ii]nfobox") then local image = mw.ustring.match(text, "|%s*image%s*=%s*([^%}|]*)") -- parse image= argument... or mw.ustring.match(text, "|%s*Cover%s*=%s*([^%}|]-)") -- or Cover= from Infobox album if image then -- add in relevant parameters: caption, alt text and image size token = "[[File:" .. image local caption = mw.ustring.match(text, "|%s*[Cc]aption%s*=%s*([^%}|]*)") if caption then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^%}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^%}|]*)") if image_size then token = token .. "|" .. image_size end token = mw.ustring.gsub(token, "\n","") .. "]]\n" end end return token end -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagenames, options) errors = options.errors if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.match(pagename, "%S.*%S") -- strip leading and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop eventually end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps for _, t in pairs {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[CcDd]n", "Citation needed", "Disambiguation needed"} do text = mw.ustring.gsub(text, "{{%s*" .. t .. "%s*|.-}}", "") -- remove ref and footnote templates end text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many [[Image: or [[File: so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if token then if inlead then -- keep comments and templates only within text body t = t .. token elseif files < maxfile then -- look for [[File:... embedded in an infobox etc. in the preamble local image = parseimage(token, false) or argimage(token) if image and checkimage(image) then -- keep comments and templates only within text body files = files + 1 if options.fileflags and options.fileflags[files] then image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not mw.ustring.match(image, "|%s*thumb%s*%f[|%]]") and not mw.ustring.match(image, "|%s*thumbnail%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else token = parseimage(text, true) if token then if files < maxfile and checkimage(token) then files = files + 1 if options.fileflags and options.fileflags[files] then if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. token end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend local endpos = math.min( mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end end end if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end until not text or text == "" or not token or token == "" text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function leadrandom(frame, israndom) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagenames = { args[1] or pargs[1] } -- For lead, ignore all but the first unnamed argument if israndom then -- For random, accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' and i > 1 then table.insert(pagenames, p) end end for i, p in pairs(pargs) do if p and type(i) == 'number' and i > 1 and not args[i] then table.insert(pagenames, p) end end end local options = {} options.paraflags = numberflags(args["paragraphs"] or pargs["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or pargs["files"] or "") -- parse file numbers options.fileargs = args["fileargs"] or pargs["fileargs"] options.more = args["more"] or pargs["more"] if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text options.errors = args["errors"] or pargs["errors"] local text = p._lead(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return leadrandom(frame, false) end function p.random(frame) return leadrandom(frame, true) end return p rv8eliqq1end256uch9j2drpgnflw8y 797202 797201 2018-05-13T12:53:35Z en>Certes 0 image bug fixes 797202 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- Check image for suitablity local function checkimage(image) local page = mw.ustring.match(image, "([Ff]ile%s*:[^|%]]*)") -- File:(name) ... or mw.ustring.match(image, "([Ii]mage%s*:[^|%]]*)") -- or Image:(name) ... if not page then return nil end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return nil end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere or at the start local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = mw.ustring.match(text, startre .. "%[%[%s*[Ff]ile%s*:.*") -- [[File: ... or mw.ustring.match(text, startre .. "%[%[%s*[Ii]mage%s*:.*") -- or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- match [[...]] to handle nesting end return image end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "%{%{%s*[Ii]nfobox") then local image = mw.ustring.match(text, "|%s*image%s*=%s*([^%}|]*)") -- parse image= argument... or mw.ustring.match(text, "|%s*Cover%s*=%s*([^%}|]-)") -- or Cover= from Infobox album if image then -- add in relevant parameters: caption, alt text and image size token = "[[File:" .. image local caption = mw.ustring.match(text, "|%s*[Cc]aption%s*=%s*([^%}|]*)") if caption then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^%}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^%}|]*)") if image_size then token = token .. "|" .. image_size end token = mw.ustring.gsub(token, "\n","") .. "]]\n" end end return token end -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagenames, options) errors = options.errors if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.match(pagename, "%S.*%S") -- strip leading and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop eventually end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps for _, t in pairs {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[CcDd]n", "Citation needed", "Disambiguation needed"} do text = mw.ustring.gsub(text, "{{%s*" .. t .. "%s*|.-}}", "") -- remove ref and footnote templates end text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many [[Image: or [[File: so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if token then if inlead then -- keep comments and templates only within text body t = t .. token elseif files < maxfile then -- look for [[File:... embedded in an infobox etc. in the preamble local image = parseimage(token, false) or argimage(token) if image and checkimage(image) then -- keep comments and templates only within text body files = files + 1 if options.fileflags and options.fileflags[files] then image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not mw.ustring.match(image, "|%s*thumb%s*%f[|%]]") and not mw.ustring.match(image, "|%s*thumbnail%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else token = parseimage(text, true) if token then if files < maxfile and checkimage(token) then files = files + 1 if options.fileflags and options.fileflags[files] then local image = token if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend local endpos = math.min( mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end end end if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end until not text or text == "" or not token or token == "" text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function leadrandom(frame, israndom) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagenames = { args[1] or pargs[1] } -- For lead, ignore all but the first unnamed argument if israndom then -- For random, accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' and i > 1 then table.insert(pagenames, p) end end for i, p in pairs(pargs) do if p and type(i) == 'number' and i > 1 and not args[i] then table.insert(pagenames, p) end end end local options = {} options.paraflags = numberflags(args["paragraphs"] or pargs["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or pargs["files"] or "") -- parse file numbers options.fileargs = args["fileargs"] or pargs["fileargs"] options.more = args["more"] or pargs["more"] if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text options.errors = args["errors"] or pargs["errors"] local text = p._lead(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return leadrandom(frame, false) end function p.random(frame) return leadrandom(frame, true) end return p 01onwy7h4f8ciedkrtifvotqei63vxj 797203 797202 2018-05-16T16:29:27Z en>Certes 0 Accept image=File:Foo or image=Image:Foo as alternatives to image=Foo 797203 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- Check image for suitablity local function checkimage(image) local page = mw.ustring.match(image, "([Ff]ile%s*:[^|%]]*)") -- File:(name) ... or mw.ustring.match(image, "([Ii]mage%s*:[^|%]]*)") -- or Image:(name) ... if not page then return nil end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return nil end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere or at the start local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = mw.ustring.match(text, startre .. "%[%[%s*[Ff]ile%s*:.*") -- [[File: ... or mw.ustring.match(text, startre .. "%[%[%s*[Ii]mage%s*:.*") -- or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- match [[...]] to handle nesting end return image end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "%{%{%s*[Ii]nfobox") then local image = mw.ustring.match(text, "|%s*image%s*=%s*([^%}|]*)") -- parse image= argument... or mw.ustring.match(text, "|%s*Cover%s*=%s*([^%}|]-)") -- or Cover= from Infobox album if image then -- add in relevant parameters: caption, alt text and image size token = "[[" -- Add File: unless name already begins File: or Image: if not (mw.ustring.match(image, "^[Ff]ile%s*:") or mw.ustring.match(image, "^[Ii]mage%s*:")) then token = token .. "File:" end token = token .. image local caption = mw.ustring.match(text, "|%s*[Cc]aption%s*=%s*([^%}|]*)") if caption then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^%}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^%}|]*)") if image_size then token = token .. "|" .. image_size end token = mw.ustring.gsub(token, "\n","") .. "]]\n" end end return token end -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagenames, options) errors = options.errors if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.match(pagename, "%S.*%S") -- strip leading and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop eventually end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps for _, t in pairs {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[CcDd]n", "Citation needed", "Disambiguation needed"} do text = mw.ustring.gsub(text, "{{%s*" .. t .. "%s*|.-}}", "") -- remove ref and footnote templates end text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many [[Image: or [[File: so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if token then if inlead then -- keep comments and templates only within text body t = t .. token elseif files < maxfile then -- look for [[File:... embedded in an infobox etc. in the preamble local image = parseimage(token, false) or argimage(token) if image and checkimage(image) then -- keep comments and templates only within text body files = files + 1 if options.fileflags and options.fileflags[files] then image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not mw.ustring.match(image, "|%s*thumb%s*%f[|%]]") and not mw.ustring.match(image, "|%s*thumbnail%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else token = parseimage(text, true) if token then if files < maxfile and checkimage(token) then files = files + 1 if options.fileflags and options.fileflags[files] then local image = token if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend local endpos = math.min( mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end end end if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end until not text or text == "" or not token or token == "" text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function leadrandom(frame, israndom) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagenames = { args[1] or pargs[1] } -- For lead, ignore all but the first unnamed argument if israndom then -- For random, accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' and i > 1 then table.insert(pagenames, p) end end for i, p in pairs(pargs) do if p and type(i) == 'number' and i > 1 and not args[i] then table.insert(pagenames, p) end end end local options = {} options.paraflags = numberflags(args["paragraphs"] or pargs["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or pargs["files"] or "") -- parse file numbers options.fileargs = args["fileargs"] or pargs["fileargs"] options.more = args["more"] or pargs["more"] if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text options.errors = args["errors"] or pargs["errors"] local text = p._lead(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return leadrandom(frame, false) end function p.random(frame) return leadrandom(frame, true) end return p lz4lvgvaljvzxbnzx1in7wdavm12miy 797204 797203 2018-05-18T11:37:39Z en>Certes 0 Adding Refn to list of removable templates 797204 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- Check image for suitablity local function checkimage(image) local page = mw.ustring.match(image, "([Ff]ile%s*:[^|%]]*)") -- File:(name) ... or mw.ustring.match(image, "([Ii]mage%s*:[^|%]]*)") -- or Image:(name) ... if not page then return nil end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return nil end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere or at the start local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = mw.ustring.match(text, startre .. "%[%[%s*[Ff]ile%s*:.*") -- [[File: ... or mw.ustring.match(text, startre .. "%[%[%s*[Ii]mage%s*:.*") -- or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- match [[...]] to handle nesting end return image end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "%{%{%s*[Ii]nfobox") then local image = mw.ustring.match(text, "|%s*image%s*=%s*([^%}|]*)") -- parse image= argument... or mw.ustring.match(text, "|%s*Cover%s*=%s*([^%}|]-)") -- or Cover= from Infobox album if image then -- add in relevant parameters: caption, alt text and image size token = "[[" -- Add File: unless name already begins File: or Image: if not (mw.ustring.match(image, "^[Ff]ile%s*:") or mw.ustring.match(image, "^[Ii]mage%s*:")) then token = token .. "File:" end token = token .. image local caption = mw.ustring.match(text, "|%s*[Cc]aption%s*=%s*([^%}|]*)") if caption then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^%}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^%}|]*)") if image_size then token = token .. "|" .. image_size end token = mw.ustring.gsub(token, "\n","") .. "]]\n" end end return token end -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagenames, options) errors = options.errors if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.match(pagename, "%S.*%S") -- strip leading and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop eventually end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps for _, t in pairs {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed"} do text = mw.ustring.gsub(text, "{{%s*" .. t .. "%s*|.-}}", "") -- remove ref and footnote templates end text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many [[Image: or [[File: so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if token then if inlead then -- keep comments and templates only within text body t = t .. token elseif files < maxfile then -- look for [[File:... embedded in an infobox etc. in the preamble local image = parseimage(token, false) or argimage(token) if image and checkimage(image) then -- keep comments and templates only within text body files = files + 1 if options.fileflags and options.fileflags[files] then image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not mw.ustring.match(image, "|%s*thumb%s*%f[|%]]") and not mw.ustring.match(image, "|%s*thumbnail%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else token = parseimage(text, true) if token then if files < maxfile and checkimage(token) then files = files + 1 if options.fileflags and options.fileflags[files] then local image = token if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend local endpos = math.min( mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end end end if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end until not text or text == "" or not token or token == "" text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function leadrandom(frame, israndom) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagenames = { args[1] or pargs[1] } -- For lead, ignore all but the first unnamed argument if israndom then -- For random, accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' and i > 1 then table.insert(pagenames, p) end end for i, p in pairs(pargs) do if p and type(i) == 'number' and i > 1 and not args[i] then table.insert(pagenames, p) end end end local options = {} options.paraflags = numberflags(args["paragraphs"] or pargs["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or pargs["files"] or "") -- parse file numbers options.fileargs = args["fileargs"] or pargs["fileargs"] options.more = args["more"] or pargs["more"] if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text options.errors = args["errors"] or pargs["errors"] local text = p._lead(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return leadrandom(frame, false) end function p.random(frame) return leadrandom(frame, true) end return p bjc6wj0naoo4h8ziyezxljo82afsp2y 797205 797204 2018-05-18T20:10:44Z en>Certes 0 Parse templates more carefully to catch unwanted templates with other templates nested inside. Also removing a few redundant % signs. 797205 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- Check image for suitablity local function checkimage(image) local page = mw.ustring.match(image, "([Ff]ile%s*:[^|%]]*)") -- File:(name) ... or mw.ustring.match(image, "([Ii]mage%s*:[^|%]]*)") -- or Image:(name) ... if not page then return nil end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return nil end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere or at the start local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = mw.ustring.match(text, startre .. "%[%[%s*[Ff]ile%s*:.*") -- [[File: ... or mw.ustring.match(text, startre .. "%[%[%s*[Ii]mage%s*:.*") -- or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- match [[...]] to handle nesting end return image end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = mw.ustring.match(text, "|%s*image%s*=%s*([^}|]*)") -- parse image= argument... or mw.ustring.match(text, "|%s*Cover%s*=%s*([^}|]-)") -- or Cover= from Infobox album if image then -- add in relevant parameters: caption, alt text and image size token = "[[" -- Add File: unless name already begins File: or Image: if not (mw.ustring.match(image, "^[Ff]ile%s*:") or mw.ustring.match(image, "^[Ii]mage%s*:")) then token = token .. "File:" end token = token .. image local caption = mw.ustring.match(text, "|%s*[Cc]aption%s*=%s*([^}|]*)") if caption then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size then token = token .. "|" .. image_size end token = mw.ustring.gsub(token, "\n","") .. "]]\n" end end return token end -- Help gsub to remove unwanted templates -- If template is unwanted then return "" (replace by nothing) else return nil (keep existing string) local function striptemplate(t) local unwanted = {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed"} for _, u in pairs(unwanted) do if mw.ustring.match(t, "^{{%s*" .. u .. "%s*%f[|}]") then return "" end -- unwanted template: remove end return nil -- not an unwanted template: keep end -- Entry point for Lua callers -- Returns a string value: text of the lead of a page function p._lead(pagenames, options) errors = options.errors if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.match(pagename, "%S.*%S") -- strip leading and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop eventually end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many [[Image: or [[File: so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if token then if inlead then -- keep comments and templates only within text body t = t .. token elseif files < maxfile then -- look for [[File:... embedded in an infobox etc. in the preamble local image = parseimage(token, false) or argimage(token) if image and checkimage(image) then -- keep comments and templates only within text body files = files + 1 if options.fileflags and options.fileflags[files] then image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not mw.ustring.match(image, "|%s*thumb%s*%f[|%]]") and not mw.ustring.match(image, "|%s*thumbnail%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else token = parseimage(text, true) if token then if files < maxfile and checkimage(token) then files = files + 1 if options.fileflags and options.fileflags[files] then local image = token if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend local endpos = math.min( mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end end end if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end until not text or text == "" or not token or token == "" text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function leadrandom(frame, israndom) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = frame.args -- from calling module local pargs = frame:getParent().args -- from template local pagenames = { args[1] or pargs[1] } -- For lead, ignore all but the first unnamed argument if israndom then -- For random, accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' and i > 1 then table.insert(pagenames, p) end end for i, p in pairs(pargs) do if p and type(i) == 'number' and i > 1 and not args[i] then table.insert(pagenames, p) end end end local options = {} options.paraflags = numberflags(args["paragraphs"] or pargs["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or pargs["files"] or "") -- parse file numbers options.fileargs = args["fileargs"] or pargs["fileargs"] options.more = args["more"] or pargs["more"] if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text options.errors = args["errors"] or pargs["errors"] local text = p._lead(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return leadrandom(frame, false) end function p.random(frame) return leadrandom(frame, true) end return p 7rfejmtv8p2i5gj4ut99vkyxirjingz 797206 797205 2018-05-19T12:05:11Z en>Certes 0 Overhaul argument handling; add support for selected article template 797206 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- Check image for suitablity local function checkimage(image) local page = mw.ustring.match(image, "([Ff]ile%s*:[^|%]]*)") -- File:(name) ... or mw.ustring.match(image, "([Ii]mage%s*:[^|%]]*)") -- or Image:(name) ... if not page then return nil end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return nil end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere or at the start local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = mw.ustring.match(text, startre .. "%[%[%s*[Ff]ile%s*:.*") -- [[File: ... or mw.ustring.match(text, startre .. "%[%[%s*[Ii]mage%s*:.*") -- or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- match [[...]] to handle nesting end return image end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = mw.ustring.match(text, "|%s*image%s*=%s*([^}|]*)") -- parse image= argument... or mw.ustring.match(text, "|%s*Cover%s*=%s*([^}|]-)") -- or Cover= from Infobox album if image then -- add in relevant parameters: caption, alt text and image size token = "[[" -- Add File: unless name already begins File: or Image: if not (mw.ustring.match(image, "^[Ff]ile%s*:") or mw.ustring.match(image, "^[Ii]mage%s*:")) then token = token .. "File:" end token = token .. image local caption = mw.ustring.match(text, "|%s*[Cc]aption%s*=%s*([^}|]*)") if caption then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size then token = token .. "|" .. image_size end token = mw.ustring.gsub(token, "\n","") .. "]]\n" end end return token end -- Help gsub to remove unwanted templates -- If template is unwanted then return "" (replace by nothing) else return nil (keep existing string) local function striptemplate(t) local unwanted = {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed"} for _, u in pairs(unwanted) do if mw.ustring.match(t, "^{{%s*" .. u .. "%s*%f[|}]") then return "" end -- unwanted template: remove end return nil -- not an unwanted template: keep end -- Entry point for Lua callers -- Returns a string value: text of the lead of a page local function main(pagenames, options) errors = options.errors if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop eventually end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many [[Image: or [[File: so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if token then if inlead then -- keep comments and templates only within text body t = t .. token elseif files < maxfile then -- look for [[File:... embedded in an infobox etc. in the preamble local image = parseimage(token, false) or argimage(token) if image and checkimage(image) then -- keep comments and templates only within text body files = files + 1 if options.fileflags and options.fileflags[files] then image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not mw.ustring.match(image, "|%s*thumb%s*%f[|%]]") and not mw.ustring.match(image, "|%s*thumbnail%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else token = parseimage(text, true) if token then if files < maxfile and checkimage(token) then files = files + 1 if options.fileflags and options.fileflags[files] then local image = token if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend local endpos = math.min( mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end end end if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end until not text or text == "" or not token or token == "" text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function invoke(frame, articlenumber) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end local pagenames = {} local articlecount = #args if articlecount < 1 then err("No articles provided") end if articlenumber then if type(articlenumber) == "string" then articlenumber = args[articlenumber] end -- normalise selected article into the range 1..#args articlenumber = articlenumber % articlecount if articlenumber == 0 then articlenumber = articlecount end pagenames = { args[articlenumber] } -- For lead, ignore all but the first unnamed argument else -- For random, accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, 1) end function p.random(frame) return invoke(frame) end function p.selected(frame) return invoke(frame, "selected") end return p nckbr74qlb3902xjkgdwfl0i5ntqoi5 797207 797206 2018-05-19T13:08:25Z en>Certes 0 Accept selected= as a string with a numeric value, e.g. "3" 797207 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- Check image for suitablity local function checkimage(image) local page = mw.ustring.match(image, "([Ff]ile%s*:[^|%]]*)") -- File:(name) ... or mw.ustring.match(image, "([Ii]mage%s*:[^|%]]*)") -- or Image:(name) ... if not page then return nil end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return nil end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere or at the start local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = mw.ustring.match(text, startre .. "%[%[%s*[Ff]ile%s*:.*") -- [[File: ... or mw.ustring.match(text, startre .. "%[%[%s*[Ii]mage%s*:.*") -- or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- match [[...]] to handle nesting end return image end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = mw.ustring.match(text, "|%s*image%s*=%s*([^}|]*)") -- parse image= argument... or mw.ustring.match(text, "|%s*Cover%s*=%s*([^}|]-)") -- or Cover= from Infobox album if image then -- add in relevant parameters: caption, alt text and image size token = "[[" -- Add File: unless name already begins File: or Image: if not (mw.ustring.match(image, "^[Ff]ile%s*:") or mw.ustring.match(image, "^[Ii]mage%s*:")) then token = token .. "File:" end token = token .. image local caption = mw.ustring.match(text, "|%s*[Cc]aption%s*=%s*([^}|]*)") if caption then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size then token = token .. "|" .. image_size end token = mw.ustring.gsub(token, "\n","") .. "]]\n" end end return token end -- Help gsub to remove unwanted templates -- If template is unwanted then return "" (replace by nothing) else return nil (keep existing string) local function striptemplate(t) local unwanted = {"[Ee]fn", "[Ee]fn-la", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed"} for _, u in pairs(unwanted) do if mw.ustring.match(t, "^{{%s*" .. u .. "%s*%f[|}]") then return "" end -- unwanted template: remove end return nil -- not an unwanted template: keep end -- Entry point for Lua callers -- Returns a string value: text of the lead of a page local function main(pagenames, options) errors = options.errors if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop eventually end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many [[Image: or [[File: so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if token then if inlead then -- keep comments and templates only within text body t = t .. token elseif files < maxfile then -- look for [[File:... embedded in an infobox etc. in the preamble local image = parseimage(token, false) or argimage(token) if image and checkimage(image) then -- keep comments and templates only within text body files = files + 1 if options.fileflags and options.fileflags[files] then image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not mw.ustring.match(image, "|%s*thumb%s*%f[|%]]") and not mw.ustring.match(image, "|%s*thumbnail%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else token = parseimage(text, true) if token then if files < maxfile and checkimage(token) then files = files + 1 if options.fileflags and options.fileflags[files] then local image = token if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend local endpos = math.min( mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end end end if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end until not text or text == "" or not token or token == "" text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function invoke(frame, articlenumber) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end local pagenames = {} local articlecount = #args if articlecount < 1 then err("No articles provided") end if articlenumber then articlenumber = tonumber(articlenumber) or tonumber(args[articlenumber]) -- normalise selected article into the range 1..#args articlenumber = articlenumber % articlecount if articlenumber == 0 then articlenumber = articlecount end pagenames = { args[articlenumber] } -- For lead, ignore all but the first unnamed argument else -- For random, accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, 1) end function p.random(frame) return invoke(frame) end function p.selected(frame) return invoke(frame, "selected") end return p ha9p9qltqqqa5l44y3xhgogc887wf7q 797208 797207 2018-05-19T14:17:22Z en>Certes 0 Supporting named article keys in Transclude selected excerpt. Dispose of Efn-ua templates. 797208 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- Check image for suitablity local function checkimage(image) local page = mw.ustring.match(image, "([Ff]ile%s*:[^|%]]*)") -- File:(name) ... or mw.ustring.match(image, "([Ii]mage%s*:[^|%]]*)") -- or Image:(name) ... if not page then return nil end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return nil end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere or at the start local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = mw.ustring.match(text, startre .. "%[%[%s*[Ff]ile%s*:.*") -- [[File: ... or mw.ustring.match(text, startre .. "%[%[%s*[Ii]mage%s*:.*") -- or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- match [[...]] to handle nesting end return image end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = mw.ustring.match(text, "|%s*image%s*=%s*([^}|]*)") -- parse image= argument... or mw.ustring.match(text, "|%s*Cover%s*=%s*([^}|]-)") -- or Cover= from Infobox album if image then -- add in relevant parameters: caption, alt text and image size token = "[[" -- Add File: unless name already begins File: or Image: if not (mw.ustring.match(image, "^[Ff]ile%s*:") or mw.ustring.match(image, "^[Ii]mage%s*:")) then token = token .. "File:" end token = token .. image local caption = mw.ustring.match(text, "|%s*[Cc]aption%s*=%s*([^}|]*)") if caption then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size then token = token .. "|" .. image_size end token = mw.ustring.gsub(token, "\n","") .. "]]\n" end end return token end -- Help gsub to remove unwanted templates -- If template is unwanted then return "" (replace by nothing) else return nil (keep existing string) local function striptemplate(t) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed"} for _, u in pairs(unwanted) do if mw.ustring.match(t, "^{{%s*" .. u .. "%s*%f[|}]") then return "" end -- unwanted template: remove end return nil -- not an unwanted template: keep end -- Entry point for Lua callers -- Returns a string value: text of the lead of a page local function main(pagenames, options) errors = options.errors if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop eventually end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many [[Image: or [[File: so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if token then if inlead then -- keep comments and templates only within text body t = t .. token elseif files < maxfile then -- look for [[File:... embedded in an infobox etc. in the preamble local image = parseimage(token, false) or argimage(token) if image and checkimage(image) then -- keep comments and templates only within text body files = files + 1 if options.fileflags and options.fileflags[files] then image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not mw.ustring.match(image, "|%s*thumb%s*%f[|%]]") and not mw.ustring.match(image, "|%s*thumbnail%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else token = parseimage(text, true) if token then if files < maxfile and checkimage(token) then files = files + 1 if options.fileflags and options.fileflags[files] then local image = token if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend local endpos = math.min( mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end end end if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end until not text or text == "" or not token or token == "" text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function invoke(frame, articlekey) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end local pagenames = {} local articlecount = #args if articlekey then -- 1 for lead template; "selected" for selected template articlekey = tonumber(articlekey) or args[articlekey] if tonumber(articlekey) then -- normalise article number into the range 1..#args if articlecount < 1 then err("No articles provided") end articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } else -- For random, accept any number of page names. If more than one, we'll pick one randomly if articlecount < 1 then err("No articles provided") end for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, 1) end function p.random(frame) return invoke(frame) end function p.selected(frame) return invoke(frame, "selected") end return p a0zlfce4j38wohf52o6r4kaqjnb2ncj 797209 797208 2018-05-20T15:25:45Z en>Certes 0 Handle {{Sfnm}} 797209 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- Check image for suitablity local function checkimage(image) local page = mw.ustring.match(image, "([Ff]ile%s*:[^|%]]*)") -- File:(name) ... or mw.ustring.match(image, "([Ii]mage%s*:[^|%]]*)") -- or Image:(name) ... if not page then return nil end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return nil end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere or at the start local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = mw.ustring.match(text, startre .. "%[%[%s*[Ff]ile%s*:.*") -- [[File: ... or mw.ustring.match(text, startre .. "%[%[%s*[Ii]mage%s*:.*") -- or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- match [[...]] to handle nesting end return image end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = mw.ustring.match(text, "|%s*image%s*=%s*([^}|]*)") -- parse image= argument... or mw.ustring.match(text, "|%s*Cover%s*=%s*([^}|]-)") -- or Cover= from Infobox album if image then -- add in relevant parameters: caption, alt text and image size token = "[[" -- Add File: unless name already begins File: or Image: if not (mw.ustring.match(image, "^[Ff]ile%s*:") or mw.ustring.match(image, "^[Ii]mage%s*:")) then token = token .. "File:" end token = token .. image local caption = mw.ustring.match(text, "|%s*[Cc]aption%s*=%s*([^}|]*)") if caption then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size then token = token .. "|" .. image_size end token = mw.ustring.gsub(token, "\n","") .. "]]\n" end end return token end -- Help gsub to remove unwanted templates -- If template is unwanted then return "" (replace by nothing) else return nil (keep existing string) local function striptemplate(t) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed"} for _, u in pairs(unwanted) do if mw.ustring.match(t, "^{{%s*" .. u .. "%s*%f[|}]") then return "" end -- unwanted template: remove end return nil -- not an unwanted template: keep end -- Entry point for Lua callers -- Returns a string value: text of the lead of a page local function main(pagenames, options) errors = options.errors if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop eventually end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many [[Image: or [[File: so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if token then if inlead then -- keep comments and templates only within text body t = t .. token elseif files < maxfile then -- look for [[File:... embedded in an infobox etc. in the preamble local image = parseimage(token, false) or argimage(token) if image and checkimage(image) then -- keep comments and templates only within text body files = files + 1 if options.fileflags and options.fileflags[files] then image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not mw.ustring.match(image, "|%s*thumb%s*%f[|%]]") and not mw.ustring.match(image, "|%s*thumbnail%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else token = parseimage(text, true) if token then if files < maxfile and checkimage(token) then files = files + 1 if options.fileflags and options.fileflags[files] then local image = token if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend local endpos = math.min( mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end end end if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end until not text or text == "" or not token or token == "" text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function invoke(frame, articlekey) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end local pagenames = {} local articlecount = #args if articlekey then -- 1 for lead template; "selected" for selected template articlekey = tonumber(articlekey) or args[articlekey] if tonumber(articlekey) then -- normalise article number into the range 1..#args if articlecount < 1 then err("No articles provided") end articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } else -- For random, accept any number of page names. If more than one, we'll pick one randomly if articlecount < 1 then err("No articles provided") end for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, 1) end function p.random(frame) return invoke(frame) end function p.selected(frame) return invoke(frame, "selected") end return p h8fw1x231357pl2cdm82b6jgmsnppn6 797210 797209 2018-05-20T16:15:53Z en>Certes 0 Limit File: to images to exclude audio files and other detritus. Parse image_flag= (Infobox country) and PD_image=. 797210 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- Check image for suitablity local function checkimage(image) local page = mw.ustring.match(image, "([Ff]ile%s*:[^|%]]*)") -- File:(name) ... or mw.ustring.match(image, "([Ii]mage%s*:[^|%]]*)") -- or Image:(name) ... if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf if not mw.ustring.match(page, "%.[Gg][Ii][Ff]%s*$") and not mw.ustring.match(page, "%.[Jj][Pp][Ee]?[Gg]%s*$") and not mw.ustring.match(page, "%.[Pp][Nn][Gg]%s*$") and not mw.ustring.match(page, "%.[Ss][Vv][Gg]%s*$") and not mw.ustring.match(page, "%.[Tt][Ii][Ff][Ff]%s*$") and not mw.ustring.match(page, "%.[Xx][Cc][Ff]%s*$") then return false end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere or at the start local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = mw.ustring.match(text, startre .. "%[%[%s*[Ff]ile%s*:.*") -- [[File: ... or mw.ustring.match(text, startre .. "%[%[%s*[Ii]mage%s*:.*") -- or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- match [[...]] to handle nesting end return image end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = mw.ustring.match(text, "|%s*image%s*=%s*([^}|]*)") -- parse image= argument... or mw.ustring.match(text, "|%s*PD_image%s*=%s*([^}|]-)") or mw.ustring.match(text, "|%s*image_flag%s*=%s*([^}|]-)") or mw.ustring.match(text, "|%s*Cover%s*=%s*([^}|]-)") -- or Cover= from Infobox album if image then -- add in relevant parameters: caption, alt text and image size token = "[[" -- Add File: unless name already begins File: or Image: if not (mw.ustring.match(image, "^[Ff]ile%s*:") or mw.ustring.match(image, "^[Ii]mage%s*:")) then token = token .. "File:" end token = token .. image local caption = mw.ustring.match(text, "|%s*[Cc]aption%s*=%s*([^}|]*)") if caption then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size then token = token .. "|" .. image_size end token = mw.ustring.gsub(token, "\n","") .. "]]\n" end end return token end -- Help gsub to remove unwanted templates -- If template is unwanted then return "" (replace by nothing) else return nil (keep existing string) local function striptemplate(t) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed"} for _, u in pairs(unwanted) do if mw.ustring.match(t, "^{{%s*" .. u .. "%s*%f[|}]") then return "" end -- unwanted template: remove end return nil -- not an unwanted template: keep end -- Entry point for Lua callers -- Returns a string value: text of the lead of a page local function main(pagenames, options) errors = options.errors if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop eventually end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first heading and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many [[Image: or [[File: so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if token then if inlead then -- keep comments and templates only within text body t = t .. token elseif files < maxfile then -- look for [[File:... embedded in an infobox etc. in the preamble local image = parseimage(token, false) or argimage(token) if image and checkimage(image) then -- keep comments and templates only within text body files = files + 1 if options.fileflags and options.fileflags[files] then image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not mw.ustring.match(image, "|%s*thumb%s*%f[|%]]") and not mw.ustring.match(image, "|%s*thumbnail%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else token = parseimage(text, true) if token then if files < maxfile and checkimage(token) then files = files + 1 if options.fileflags and options.fileflags[files] then local image = token if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend local endpos = math.min( mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end end end if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end until not text or text == "" or not token or token == "" text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function invoke(frame, articlekey) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end local pagenames = {} local articlecount = #args if articlekey then -- 1 for lead template; "selected" for selected template articlekey = tonumber(articlekey) or args[articlekey] if tonumber(articlekey) then -- normalise article number into the range 1..#args if articlecount < 1 then err("No articles provided") end articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } else -- For random, accept any number of page names. If more than one, we'll pick one randomly if articlecount < 1 then err("No articles provided") end for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, 1) end function p.random(frame) return invoke(frame) end function p.selected(frame) return invoke(frame, "selected") end return p 5f1ofyalno34xmw11p30cdvcu6ia6en 797211 797210 2018-05-20T20:22:53Z en>Certes 0 Improve comments; minor bug fix (parsing [[Image:...]] within paragraph) 797211 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- Check image for suitability local function checkimage(image) local page = mw.ustring.match(image, "([Ff]ile%s*:[^|%]]*)") -- File:(name) ... or mw.ustring.match(image, "([Ii]mage%s*:[^|%]]*)") -- or Image:(name) ... if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not mw.ustring.match(page, "%.[Gg][Ii][Ff]%s*$") and not mw.ustring.match(page, "%.[Jj][Pp][Ee]?[Gg]%s*$") and not mw.ustring.match(page, "%.[Pp][Nn][Gg]%s*$") and not mw.ustring.match(page, "%.[Ss][Vv][Gg]%s*$") and not mw.ustring.match(page, "%.[Tt][Ii][Ff][Ff]%s*$") and not mw.ustring.match(page, "%.[Xx][Cc][Ff]%s*$") then return false end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = mw.ustring.match(text, startre .. "%[%[%s*[Ff]ile%s*:.*") -- [[File: ... or mw.ustring.match(text, startre .. "%[%[%s*[Ii]mage%s*:.*") -- or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = mw.ustring.match(text, "|%s*image%s*=%s*([^}|]*)") -- parse image= argument... or mw.ustring.match(text, "|%s*PD_image%s*=%s*([^}|]-)") -- or its known alternatives such as... or mw.ustring.match(text, "|%s*image_flag%s*=%s*([^}|]-)") -- image_flag= from Infobox country or mw.ustring.match(text, "|%s*Cover%s*=%s*([^}|]-)") -- or Cover= from Infobox album if image then -- add in relevant optional parameters: caption, alt text and image size token = "[[" -- Add File: unless name already begins File: or Image: if not (mw.ustring.match(image, "^[Ff]ile%s*:") or mw.ustring.match(image, "^[Ii]mage%s*:")) then token = token .. "File:" end token = token .. image local caption = mw.ustring.match(text, "|%s*[Cc]aption%s*=%s*([^}|]*)") if caption then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size then token = token .. "|" .. image_size end token = mw.ustring.gsub(token, "\n","") .. "]]\n" end end return token end -- Help gsub to remove unwanted templates -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local function striptemplate(t) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed"} for _, u in pairs(unwanted) do if mw.ustring.match(t, "^{{%s*" .. u .. "%s*%f[|}]") then return "" end -- unwanted template: remove end return nil -- not an unwanted template: keep end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) errors = options.errors -- set the module level boolean used in local function err if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if token then -- found a template if inlead then -- lead has already started, so keep the template within the text t = t .. token elseif files < maxfile then -- discard template, but if we are still collecting images... local image = parseimage(token, false) or argimage(token) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not mw.ustring.match(image, "|%s*thumb%s*%f[|%]]") and not mw.ustring.match(image, "|%s*thumbnail%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true -- we got a paragraph, so we are inside the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function invoke(frame, articlekey) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template local pagenames = {} local articlecount = #args if articlekey then -- 1 for lead template; "selected" for selected template articlekey = tonumber(articlekey) or args[articlekey] if tonumber(articlekey) then -- normalise article number into the range 1..#args if articlecount < 1 then err("No articles provided") end articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } else -- For random, accept any number of page names. If more than one, we'll pick one randomly if articlecount < 1 then err("No articles provided") end for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, 1) end -- {{Transclude lead article}} reads the first and only article function p.random(frame) return invoke(frame) end -- {{Transclude random article}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected article}} reads the article whose key is in the selected= parameter return p jdntz2yjaa9ar69qy1me02m4wq8dt7c 797212 797211 2018-05-20T21:37:11Z en>Certes 0 Make the bold title near the start of the article into a wikilink to the article 797212 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- Check image for suitability local function checkimage(image) local page = mw.ustring.match(image, "([Ff]ile%s*:[^|%]]*)") -- File:(name) ... or mw.ustring.match(image, "([Ii]mage%s*:[^|%]]*)") -- or Image:(name) ... if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not mw.ustring.match(page, "%.[Gg][Ii][Ff]%s*$") and not mw.ustring.match(page, "%.[Jj][Pp][Ee]?[Gg]%s*$") and not mw.ustring.match(page, "%.[Pp][Nn][Gg]%s*$") and not mw.ustring.match(page, "%.[Ss][Vv][Gg]%s*$") and not mw.ustring.match(page, "%.[Tt][Ii][Ff][Ff]%s*$") and not mw.ustring.match(page, "%.[Xx][Cc][Ff]%s*$") then return false end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = mw.ustring.match(text, startre .. "%[%[%s*[Ff]ile%s*:.*") -- [[File: ... or mw.ustring.match(text, startre .. "%[%[%s*[Ii]mage%s*:.*") -- or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = mw.ustring.match(text, "|%s*image%s*=%s*([^}|]*)") -- parse image= argument... or mw.ustring.match(text, "|%s*PD_image%s*=%s*([^}|]-)") -- or its known alternatives such as... or mw.ustring.match(text, "|%s*image_flag%s*=%s*([^}|]-)") -- image_flag= from Infobox country or mw.ustring.match(text, "|%s*Cover%s*=%s*([^}|]-)") -- or Cover= from Infobox album if image then -- add in relevant optional parameters: caption, alt text and image size token = "[[" -- Add File: unless name already begins File: or Image: if not (mw.ustring.match(image, "^[Ff]ile%s*:") or mw.ustring.match(image, "^[Ii]mage%s*:")) then token = token .. "File:" end token = token .. image local caption = mw.ustring.match(text, "|%s*[Cc]aption%s*=%s*([^}|]*)") if caption then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size then token = token .. "|" .. image_size end token = mw.ustring.gsub(token, "\n","") .. "]]\n" end end return token end -- Help gsub to remove unwanted templates -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local function striptemplate(t) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed"} for _, u in pairs(unwanted) do if mw.ustring.match(t, "^{{%s*" .. u .. "%s*%f[|}]") then return "" end -- unwanted template: remove end return nil -- not an unwanted template: keep end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) errors = options.errors -- set the module level boolean used in local function err if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end pagename = redir or pagename text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if token then -- found a template if inlead then -- lead has already started, so keep the template within the text t = t .. token elseif files < maxfile then -- discard template, but if we are still collecting images... local image = parseimage(token, false) or argimage(token) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not mw.ustring.match(image, "|%s*thumb%s*%f[|%]]") and not mw.ustring.match(image, "|%s*thumbnail%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true -- we got a paragraph, so we are inside the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-'''+)(.-)'''", function(a, b) -- replace '''Foo''' by '''[[pagename|Foo]] if early in article and not wikilinked if mw.ustring.len(a) < 100 and not mw.ustring.find(b, "%[") then return a .. "[[" .. pagename .. "|" .. b .. "]]'''" else return nil end end, 1) end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function invoke(frame, articlekey) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template local pagenames = {} local articlecount = #args if articlekey then -- 1 for lead template; "selected" for selected template articlekey = tonumber(articlekey) or args[articlekey] if tonumber(articlekey) then -- normalise article number into the range 1..#args if articlecount < 1 then err("No articles provided") end articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } else -- For random, accept any number of page names. If more than one, we'll pick one randomly if articlecount < 1 then err("No articles provided") end for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, 1) end -- {{Transclude lead article}} reads the first and only article function p.random(frame) return invoke(frame) end -- {{Transclude random article}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected article}} reads the article whose key is in the selected= parameter return p 697lmukgsima0ntxcy565r2h3a2syj0 797213 797212 2018-05-22T11:04:48Z en>Certes 0 Bug fix: display title correctly where only part of the bold text is in italics, e.g. '''Foo ''Bar''''' or '''''Foo'' Bar''' 797213 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- Check image for suitability local function checkimage(image) local page = mw.ustring.match(image, "([Ff]ile%s*:[^|%]]*)") -- File:(name) ... or mw.ustring.match(image, "([Ii]mage%s*:[^|%]]*)") -- or Image:(name) ... if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not mw.ustring.match(page, "%.[Gg][Ii][Ff]%s*$") and not mw.ustring.match(page, "%.[Jj][Pp][Ee]?[Gg]%s*$") and not mw.ustring.match(page, "%.[Pp][Nn][Gg]%s*$") and not mw.ustring.match(page, "%.[Ss][Vv][Gg]%s*$") and not mw.ustring.match(page, "%.[Tt][Ii][Ff][Ff]%s*$") and not mw.ustring.match(page, "%.[Xx][Cc][Ff]%s*$") then return false end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = mw.ustring.match(text, startre .. "%[%[%s*[Ff]ile%s*:.*") -- [[File: ... or mw.ustring.match(text, startre .. "%[%[%s*[Ii]mage%s*:.*") -- or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = mw.ustring.match(text, "|%s*image%s*=%s*([^}|]*)") -- parse image= argument... or mw.ustring.match(text, "|%s*PD_image%s*=%s*([^}|]-)") -- or its known alternatives such as... or mw.ustring.match(text, "|%s*image_flag%s*=%s*([^}|]-)") -- image_flag= from Infobox country or mw.ustring.match(text, "|%s*Cover%s*=%s*([^}|]-)") -- or Cover= from Infobox album if image then -- add in relevant optional parameters: caption, alt text and image size token = "[[" -- Add File: unless name already begins File: or Image: if not (mw.ustring.match(image, "^[Ff]ile%s*:") or mw.ustring.match(image, "^[Ii]mage%s*:")) then token = token .. "File:" end token = token .. image local caption = mw.ustring.match(text, "|%s*[Cc]aption%s*=%s*([^}|]*)") if caption then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size then token = token .. "|" .. image_size end token = mw.ustring.gsub(token, "\n","") .. "]]\n" end end return token end -- Help gsub to remove unwanted templates -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local function striptemplate(t) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed"} for _, u in pairs(unwanted) do if mw.ustring.match(t, "^{{%s*" .. u .. "%s*%f[|}]") then return "" end -- unwanted template: remove end return nil -- not an unwanted template: keep end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) errors = options.errors -- set the module level boolean used in local function err if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end pagename = redir or pagename text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if token then -- found a template if inlead then -- lead has already started, so keep the template within the text t = t .. token elseif files < maxfile then -- discard template, but if we are still collecting images... local image = parseimage(token, false) or argimage(token) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not mw.ustring.match(image, "|%s*thumb%s*%f[|%]]") and not mw.ustring.match(image, "|%s*thumbnail%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true -- we got a paragraph, so we are inside the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) -- replace '''Foo''' by '''[[pagename|Foo]] if early in article and not wikilinked if mw.ustring.len(a) < 100 and not mw.ustring.find(b, "%[") then return a .. "[[" .. pagename .. "|" .. b .. "]]'''" else return nil end end, 1) end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function invoke(frame, articlekey) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template local pagenames = {} local articlecount = #args if articlekey then -- 1 for lead template; "selected" for selected template articlekey = tonumber(articlekey) or args[articlekey] if tonumber(articlekey) then -- normalise article number into the range 1..#args if articlecount < 1 then err("No articles provided") end articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } else -- For random, accept any number of page names. If more than one, we'll pick one randomly if articlecount < 1 then err("No articles provided") end for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, 1) end -- {{Transclude lead article}} reads the first and only article function p.random(frame) return invoke(frame) end -- {{Transclude random article}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected article}} reads the article whose key is in the selected= parameter return p cp4j9zgrzqnyljw2sxjakknpr420pjy 797214 797213 2018-05-22T12:16:05Z en>Certes 0 Prefer image= to [[File:... when extracting an image from an infobox with multiple images. Accept non-standard argument names such as Ship image= and Ship caption=. Refactor to use matchany(). 797214 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover"}, "%s*=%s*(.*)") if image then -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*([^}|]*)") if caption then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" end end return token end -- Help gsub to remove unwanted templates -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local function striptemplate(t) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed"} for _, u in pairs(unwanted) do if mw.ustring.match(t, "^{{%s*" .. u .. "%s*%f[|}]") then return "" end -- unwanted template: remove end return nil -- not an unwanted template: keep end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) errors = options.errors -- set the module level boolean used in local function err if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end pagename = redir or pagename text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if token then -- found a template if inlead then -- lead has already started, so keep the template within the text t = t .. token elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true -- we got a paragraph, so we are inside the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) -- replace '''Foo''' by '''[[pagename|Foo]] if early in article and not wikilinked if mw.ustring.len(a) < 100 and not mw.ustring.find(b, "%[") then return a .. "[[" .. pagename .. "|" .. b .. "]]'''" else return nil end end, 1) end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function invoke(frame, articlekey) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template local pagenames = {} local articlecount = #args if articlekey then -- 1 for lead template; "selected" for selected template articlekey = tonumber(articlekey) or args[articlekey] if tonumber(articlekey) then -- normalise article number into the range 1..#args if articlecount < 1 then err("No articles provided") end articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } else -- For random, accept any number of page names. If more than one, we'll pick one randomly if articlecount < 1 then err("No articles provided") end for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, 1) end -- {{Transclude lead article}} reads the first and only article function p.random(frame) return invoke(frame) end -- {{Transclude random article}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected article}} reads the article whose key is in the selected= parameter return p 18pz41o9gql85pveyhaphhvil3myy9m 797215 797214 2018-05-22T14:36:43Z en>Certes 0 Parse caption found in infobox better, to skip pipes in nested wikilinks 797215 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover"}, "%s*=%s*(.*)") if image then -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = parsecaption(matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*(.*)")) if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" end end return token end -- Help gsub to remove unwanted templates -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local function striptemplate(t) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed"} for _, u in pairs(unwanted) do if mw.ustring.match(t, "^{{%s*" .. u .. "%s*%f[|}]") then return "" end -- unwanted template: remove end return nil -- not an unwanted template: keep end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) errors = options.errors -- set the module level boolean used in local function err if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end pagename = redir or pagename text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end -- a basic parser to trim down the lead local inlead = false -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if token then -- found a template if inlead then -- lead has already started, so keep the template within the text t = t .. token elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end inlead = true -- we got a paragraph, so we are inside the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) -- replace '''Foo''' by '''[[pagename|Foo]] if early in article and not wikilinked if mw.ustring.len(a) < 100 and not mw.ustring.find(b, "%[") then return a .. "[[" .. pagename .. "|" .. b .. "]]'''" else return nil end end, 1) end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function invoke(frame, articlekey) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template local pagenames = {} local articlecount = #args if articlekey then -- 1 for lead template; "selected" for selected template articlekey = tonumber(articlekey) or args[articlekey] if tonumber(articlekey) then -- normalise article number into the range 1..#args if articlecount < 1 then err("No articles provided") end articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } else -- For random, accept any number of page names. If more than one, we'll pick one randomly if articlecount < 1 then err("No articles provided") end for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, 1) end -- {{Transclude lead article}} reads the first and only article function p.random(frame) return invoke(frame) end -- {{Transclude random article}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected article}} reads the article whose key is in the selected= parameter return p kcpk1vz94deb46ry8oge23fpbjf83bq 797216 797215 2018-05-22T14:57:58Z en>Certes 0 When checking whether bold text is early enough to wikilink to the article as a synonym for its title, disregard text before the lead such as image links. 797216 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover"}, "%s*=%s*(.*)") if image then -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = parsecaption(matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*(.*)")) if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" end end return token end -- Help gsub to remove unwanted templates -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local function striptemplate(t) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed"} for _, u in pairs(unwanted) do if mw.ustring.match(t, "^{{%s*" .. u .. "%s*%f[|}]") then return "" end -- unwanted template: remove end return nil -- not an unwanted template: keep end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) errors = options.errors -- set the module level boolean used in local function err if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end pagename = redir or pagename text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end -- a basic parser to trim down the lead local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if token then -- found a template if leadstart then -- lead has already started, so keep the template within the text t = t .. token elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function invoke(frame, articlekey) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template local pagenames = {} local articlecount = #args if articlekey then -- 1 for lead template; "selected" for selected template articlekey = tonumber(articlekey) or args[articlekey] if tonumber(articlekey) then -- normalise article number into the range 1..#args if articlecount < 1 then err("No articles provided") end articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } else -- For random, accept any number of page names. If more than one, we'll pick one randomly if articlecount < 1 then err("No articles provided") end for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, 1) end -- {{Transclude lead article}} reads the first and only article function p.random(frame) return invoke(frame) end -- {{Transclude random article}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected article}} reads the article whose key is in the selected= parameter return p dzw8w8s8cbp975rzozatrz59s5f2oxd 797217 797216 2018-05-23T12:43:59Z en>Certes 0 Remove Featured Article star more vigorously 797217 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover"}, "%s*=%s*(.*)") if image then -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = parsecaption(matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*(.*)")) if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" end end return token end -- Help gsub to remove unwanted templates -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local function striptemplate(t) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article"} for _, u in pairs(unwanted) do if mw.ustring.match(t, "^{{%s*" .. u .. "%s*%f[|}]") then return "" end -- unwanted template: remove end return nil -- not an unwanted template: keep end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) errors = options.errors -- set the module level boolean used in local function err if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end pagename = redir or pagename text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end -- a basic parser to trim down the lead local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if token then -- found a template if leadstart then -- lead has already started, so keep the template within the text t = t .. token elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function invoke(frame, articlekey) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template local pagenames = {} local articlecount = #args if articlekey then -- 1 for lead template; "selected" for selected template articlekey = tonumber(articlekey) or args[articlekey] if tonumber(articlekey) then -- normalise article number into the range 1..#args if articlecount < 1 then err("No articles provided") end articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } else -- For random, accept any number of page names. If more than one, we'll pick one randomly if articlecount < 1 then err("No articles provided") end for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, 1) end -- {{Transclude lead article}} reads the first and only article function p.random(frame) return invoke(frame) end -- {{Transclude random article}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected article}} reads the article whose key is in the selected= parameter return p pu1layw9sy56mbbrag7ctduvte3h06e 797218 797217 2018-05-24T22:27:10Z en>Certes 0 Remove sidebars concealed within the lead 797218 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover"}, "%s*=%s*(.*)") if image then -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = parsecaption(matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*(.*)")) if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" end end return token end -- Help gsub to remove unwanted templates -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local function striptemplate(t) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article"} for _, u in pairs(unwanted) do if mw.ustring.match(t, "^{{%s*" .. u .. "%s*%f[|}]") then return "" end -- unwanted template: remove end return nil -- not an unwanted template: keep end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) errors = options.errors -- set the module level boolean used in local function err if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end pagename = redir or pagename text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*{{[^{|}]*sidebar%s*}}", "") -- remove most sidebars local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end -- a basic parser to trim down the lead local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if token then -- found a template if leadstart then -- lead has already started, so keep the template within the text t = t .. token elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function invoke(frame, articlekey) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template local pagenames = {} local articlecount = #args if articlekey then -- 1 for lead template; "selected" for selected template articlekey = tonumber(articlekey) or args[articlekey] if tonumber(articlekey) then -- normalise article number into the range 1..#args if articlecount < 1 then err("No articles provided") end articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } else -- For random, accept any number of page names. If more than one, we'll pick one randomly if articlecount < 1 then err("No articles provided") end for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, 1) end -- {{Transclude lead article}} reads the first and only article function p.random(frame) return invoke(frame) end -- {{Transclude random article}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected article}} reads the article whose key is in the selected= parameter return p i6n9imjzo2sgz506bptttfnkkkz7wu2 797219 797218 2018-05-25T10:52:11Z en>Certes 0 Strip |ref parameter from {{UN Population|ref}} and similar 797219 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover"}, "%s*=%s*(.*)") if image then -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = parsecaption(matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*(.*)")) if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" end end return token end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) errors = options.errors -- set the module level boolean used in local function err if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end pagename = redir or pagename text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*{{[^{|}]*sidebar%s*}}", "") -- remove most sidebars local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end -- a basic parser to trim down the lead local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if token then -- found a template if leadstart then -- lead has already started, so keep the template within the text t = t .. token elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function invoke(frame, articlekey) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template local pagenames = {} local articlecount = #args if articlekey then -- 1 for lead template; "selected" for selected template articlekey = tonumber(articlekey) or args[articlekey] if tonumber(articlekey) then -- normalise article number into the range 1..#args if articlecount < 1 then err("No articles provided") end articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } else -- For random, accept any number of page names. If more than one, we'll pick one randomly if articlecount < 1 then err("No articles provided") end for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, 1) end -- {{Transclude lead article}} reads the first and only article function p.random(frame) return invoke(frame) end -- {{Transclude random article}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected article}} reads the article whose key is in the selected= parameter return p kq3o5o4hyi3bcm9tmads9lzziuye12w 797220 797219 2018-05-29T21:40:54Z en>Certes 0 Treat templates at the start of a line of text as part of that text 797220 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover"}, "%s*=%s*(.*)") if image then -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = parsecaption(matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*(.*)")) if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" end end return token end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) errors = options.errors -- set the module level boolean used in local function err if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end pagename = redir or pagename text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*{{[^{|}]*sidebar%s*}}", "") -- remove most sidebars local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end -- a basic parser to trim down the lead local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line if mw.ustring.find(line, "%S") then token = nil end -- if anything is left, keep the template: it counts as part of the line end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text t = t .. token elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function invoke(frame, articlekey) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template local pagenames = {} local articlecount = #args if articlekey then -- 1 for lead template; "selected" for selected template articlekey = tonumber(articlekey) or args[articlekey] if tonumber(articlekey) then -- normalise article number into the range 1..#args if articlecount < 1 then err("No articles provided") end articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } else -- For random, accept any number of page names. If more than one, we'll pick one randomly if articlecount < 1 then err("No articles provided") end for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, 1) end -- {{Transclude lead article}} reads the first and only article function p.random(frame) return invoke(frame) end -- {{Transclude random article}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected article}} reads the article whose key is in the selected= parameter return p fpjmua9x00h34idbiwwth3i73cgglf1 797221 797220 2018-05-30T00:09:20Z en>Certes 0 Discard template if immediately followed by another template which spans lines 797221 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover"}, "%s*=%s*(.*)") if image then -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = parsecaption(matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*(.*)")) if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" end end return token end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) errors = options.errors -- set the module level boolean used in local function err if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end pagename = redir or pagename text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*{{[^{|}]*sidebar%s*}}", "") -- remove most sidebars local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end -- a basic parser to trim down the lead local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line if mw.ustring.find(line, "%S") and not mw.ustring.find(line, "^%s*{{") then token = nil end -- if anything is left, other than an incomplete further template, keep the template: it counts as part of the line end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text t = t .. token elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function invoke(frame, articlekey) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template local pagenames = {} local articlecount = #args if articlekey then -- 1 for lead template; "selected" for selected template articlekey = tonumber(articlekey) or args[articlekey] if tonumber(articlekey) then -- normalise article number into the range 1..#args if articlecount < 1 then err("No articles provided") end articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } else -- For random, accept any number of page names. If more than one, we'll pick one randomly if articlecount < 1 then err("No articles provided") end for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, 1) end -- {{Transclude lead article}} reads the first and only article function p.random(frame) return invoke(frame) end -- {{Transclude random article}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected article}} reads the article whose key is in the selected= parameter return p rd0kokahqmqb1m8ulqa1l75gj1dp079 797222 797221 2018-05-30T00:16:23Z en>Certes 0 Remove templates before the lead even if followed by an image on the same line 797222 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover"}, "%s*=%s*(.*)") if image then -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = parsecaption(matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*(.*)")) if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" end end return token end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) errors = options.errors -- set the module level boolean used in local function err if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end pagename = redir or pagename text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*{{[^{|}]*sidebar%s*}}", "") -- remove most sidebars local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end -- a basic parser to trim down the lead local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text t = t .. token elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function invoke(frame, articlekey) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template local pagenames = {} local articlecount = #args if articlekey then -- 1 for lead template; "selected" for selected template articlekey = tonumber(articlekey) or args[articlekey] if tonumber(articlekey) then -- normalise article number into the range 1..#args if articlecount < 1 then err("No articles provided") end articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } else -- For random, accept any number of page names. If more than one, we'll pick one randomly if articlecount < 1 then err("No articles provided") end for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, 1) end -- {{Transclude lead article}} reads the first and only article function p.random(frame) return invoke(frame) end -- {{Transclude random article}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected article}} reads the article whose key is in the selected= parameter return p d5h2wx4m2jyyaq30rapo7x7mvgiv8zq 797223 797222 2018-05-30T15:13:25Z en>Certes 0 Remove protection templates 797223 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover"}, "%s*=%s*(.*)") if image then -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = parsecaption(matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*(.*)")) if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" end end return token end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) errors = options.errors -- set the module level boolean used in local function err if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end pagename = redir or pagename text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*sidebar%s*}}", "") -- remove most sidebars local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end -- a basic parser to trim down the lead local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text t = t .. token elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Shared template invocation code for lead and random functions local function invoke(frame, articlekey) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template local pagenames = {} local articlecount = #args if articlekey then -- 1 for lead template; "selected" for selected template articlekey = tonumber(articlekey) or args[articlekey] if tonumber(articlekey) then -- normalise article number into the range 1..#args if articlecount < 1 then err("No articles provided") end articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } else -- For random, accept any number of page names. If more than one, we'll pick one randomly if articlecount < 1 then err("No articles provided") end for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, 1) end -- {{Transclude lead article}} reads the first and only article function p.random(frame) return invoke(frame) end -- {{Transclude random article}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected article}} reads the article whose key is in the selected= parameter return p adap53n6kb0gb555bjls2fw19z8ssiq 797224 797223 2018-05-31T11:01:27Z en>Certes 0 Adding linked function; refactor invoke() 797224 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover"}, "%s*=%s*(.*)") if image then -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = parsecaption(matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*(.*)")) if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" end end return token end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end pagename = redir or pagename text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*sidebar%s*}}", "") -- remove most sidebars local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end -- a basic parser to trim down the lead local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text t = t .. token elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Parse a ==Section== from a page local function getsection(text, section) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" then -- Read named page and find its wikilinks local page = args[1] local title = mw.title.new(page) -- Read the named page if not title then return err("No title for page name " .. page) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end text = title:getContent() if not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end for p in mw.ustring.gmatch(text, "%[%[([^%]|#\n]*)") do table.insert(pagenames, p) end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead article}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked article}} reads a randomly selected article linked from the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random article}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected article}} reads the article whose key is in the selected= parameter return p 3ilkcz37cds35krla7nvffaj43h6k6e 797225 797224 2018-06-01T17:23:28Z en>Certes 0 Remove "clarification needed", "Primary source inline" and their dozens of aliases 797225 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover"}, "%s*=%s*(.*)") if image then -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = parsecaption(matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*(.*)")) if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" end end return token end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end pagename = redir or pagename text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*sidebar%s*}}", "") -- remove most sidebars local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end -- a basic parser to trim down the lead local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text t = t .. token elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Parse a ==Section== from a page local function getsection(text, section) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" then -- Read named page and find its wikilinks local page = args[1] local title = mw.title.new(page) -- Read the named page if not title then return err("No title for page name " .. page) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end text = title:getContent() if not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end for p in mw.ustring.gmatch(text, "%[%[([^%]|#\n]*)") do table.insert(pagenames, p) end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead article}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked article}} reads a randomly selected article linked from the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random article}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected article}} reads the article whose key is in the selected= parameter return p t8q34r6qcog6u9wb06y75ba42qip9qi 797226 797225 2018-06-06T14:32:28Z en>Waggers 0 added "static_image_name" as a possible Infobox image parameter 797226 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover", "static_image_name"}, "%s*=%s*(.*)") if image then -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = parsecaption(matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*(.*)")) if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" end end return token end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end pagename = redir or pagename text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*sidebar%s*}}", "") -- remove most sidebars local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end -- a basic parser to trim down the lead local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text t = t .. token elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Parse a ==Section== from a page local function getsection(text, section) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" then -- Read named page and find its wikilinks local page = args[1] local title = mw.title.new(page) -- Read the named page if not title then return err("No title for page name " .. page) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end text = title:getContent() if not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end for p in mw.ustring.gmatch(text, "%[%[([^%]|#\n]*)") do table.insert(pagenames, p) end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead article}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked article}} reads a randomly selected article linked from the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random article}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected article}} reads the article whose key is in the selected= parameter return p hyu2g7xovefs13lk4fapojmmbipp3w0 797227 797226 2018-06-06T14:38:34Z en>Waggers 0 added "static_image" as a possible Infobox image parameter 797227 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover", "static_image_name", "static_image"}, "%s*=%s*(.*)") if image then -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = parsecaption(matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*(.*)")) if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" end end return token end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end pagename = redir or pagename text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*sidebar%s*}}", "") -- remove most sidebars local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end -- a basic parser to trim down the lead local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text t = t .. token elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Parse a ==Section== from a page local function getsection(text, section) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" then -- Read named page and find its wikilinks local page = args[1] local title = mw.title.new(page) -- Read the named page if not title then return err("No title for page name " .. page) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end text = title:getContent() if not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end for p in mw.ustring.gmatch(text, "%[%[([^%]|#\n]*)") do table.insert(pagenames, p) end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead article}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked article}} reads a randomly selected article linked from the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random article}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected article}} reads the article whose key is in the selected= parameter return p tq3xv8ehflquzafs1rkdwn1ahbt5rdd 797228 797227 2018-06-06T14:42:11Z en>Waggers 0 added other possible image parameters from {{Infobox UK place}} 797228 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover", "static_image_name", "static_image", "image_skyline", "image_shield", "static_image2_name", "static_image2"}, "%s*=%s*(.*)") if image then -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = parsecaption(matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*(.*)")) if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" end end return token end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end pagename = redir or pagename text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*sidebar%s*}}", "") -- remove most sidebars local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end -- a basic parser to trim down the lead local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text t = t .. token elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Parse a ==Section== from a page local function getsection(text, section) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" then -- Read named page and find its wikilinks local page = args[1] local title = mw.title.new(page) -- Read the named page if not title then return err("No title for page name " .. page) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end text = title:getContent() if not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end for p in mw.ustring.gmatch(text, "%[%[([^%]|#\n]*)") do table.insert(pagenames, p) end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead article}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked article}} reads a randomly selected article linked from the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random article}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected article}} reads the article whose key is in the selected= parameter return p a4zm7lqylniu7r8712gyr811zjn5nfx 797229 797228 2018-06-09T16:45:11Z en>Certes 0 Adding listitem function; minor fixes 797229 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover", "static_image_name", "static_image", "image_skyline", "image_shield", "static_image2_name", "static_image2"}, "%s*=%s*(.*)") if image then -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = parsecaption(matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*(.*)")) if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" end end return token end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end pagename = redir or pagename text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*sidebar%s*}}", "") -- remove most sidebars local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end -- a basic parser to trim down the lead local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text t = t .. token elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Parse a ==Section== from a page local function getsection(text, section) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local title = mw.title.new(page) -- Read the named page if not title then return err("No title for page name " .. page) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local text = title:getContent() if not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter return p or1dsm57fx50tbs4h5cf55arbo094dq 797230 797229 2018-06-10T23:45:50Z en>Certes 0 Remove stub templates and categories because, in a stub, the whole article resembles a lead. 797230 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local title = mw.title.new(":" .. page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local frame = mw.getCurrentFrame() local desc = frame:preprocess("{{" .. title.prefixedText .. "}}") return desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover", "static_image_name", "static_image", "image_skyline", "image_shield", "static_image2_name", "static_image2"}, "%s*=%s*(.*)") if image then -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = parsecaption(matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*(.*)")) if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" end end return token end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local title = mw.title.new(pagename) -- Find the lead section of the named page if not title then return err("No title for page name " .. pagename) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end pagename = redir or pagename text = title:getContent() end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*sidebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-stub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories local allparas = true -- keep all paragraphs? if options.paraflags then for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end -- a basic parser to trim down the lead local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text t = t .. token elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Parse a ==Section== from a page local function getsection(text, section) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local title = mw.title.new(page) -- Read the named page if not title then return err("No title for page name " .. page) end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end local text = title:getContent() if not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter return p k8fqtqhal3rk2b94o50yp8q90f6i6sw 797231 797230 2018-06-12T08:28:06Z en>Evad37 0 generalise some functions, and provide entry points for other Lua modules 797231 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if t local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover", "static_image_name", "static_image", "image_skyline", "image_shield", "static_image2_name", "static_image2"}, "%s*=%s*(.*)") if image then -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = parsecaption(matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*(.*)")) if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" end end return token end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*sidebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-stub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then text, noramliesdPagename = getContent(pagename) if not noramliesdPagename then return err("No title for page name " .. pagename) else pagename = noramliesdPagename end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Parse a ==Section== from a page local function getsection(text, section) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end return p ban2yzogesyve32cpttoor5x4msxkdx 797232 797231 2018-06-14T10:59:38Z en>Certes 0 Remove disambiguation templates 797232 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if t local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover", "static_image_name", "static_image", "image_skyline", "image_shield", "static_image2_name", "static_image2"}, "%s*=%s*(.*)") if image then -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = parsecaption(matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*(.*)")) if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" end end return token end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w%s]-%f[%w][Dd]isam[%w%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*sidebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-stub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then text, noramliesdPagename = getContent(pagename) if not noramliesdPagename then return err("No title for page name " .. pagename) else pagename = noramliesdPagename end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Parse a ==Section== from a page local function getsection(text, section) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end return p tn0zwihf3ym35i8012kfzebl69epfdj 797233 797232 2018-06-18T05:15:03Z en>Evad37 0 additional entry points for modules 797233 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if t local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil if mw.ustring.match(text, "{{%s*[Ii]nfobox") then local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover", "static_image_name", "static_image", "image_skyline", "image_shield", "static_image2_name", "static_image2"}, "%s*=%s*(.*)") if image then -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = parsecaption(matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*(.*)")) if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" end end return token end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w%s]-%f[%w][Dd]isam[%w%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*sidebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-stub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then text, noramliesdPagename = getContent(pagename) if not noramliesdPagename then return err("No title for page name " .. pagename) else pagename = noramliesdPagename end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Parse a ==Section== from a page local function getsection(text, section) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p galx4ey8v0fydd6f9xv7zr0w3vqq0ik 797234 797233 2018-06-18T06:15:12Z en>Evad37 0 catch infoboxes with other names (e.g. {{subspeciesbox}}) 797234 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if t local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local isInfobox = mw.ustring.match(text, "{{%s*[Ii]nfobox") if not isInfobox then -- check by expanding template, to catch infoboxes with other names (e.g. {{subspeciesbox}}) local frame = mw.getCurrentFrame() local expandedContent = frame:preprocess(text) isInfobox = mw.ustring.match(expandedContent, "<table[^>]-infobox") or mw.ustring.match(expandedContent, "{|[^\n]*infobox") end if isInfobox then local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover", "static_image_name", "static_image", "image_skyline", "image_shield", "static_image2_name", "static_image2"}, "%s*=%s*(.*)") if image then -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = parsecaption(matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*(.*)")) if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" end end return token end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w%s]-%f[%w][Dd]isam[%w%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*sidebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-stub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then text, noramliesdPagename = getContent(pagename) if not noramliesdPagename then return err("No title for page name " .. pagename) else pagename = noramliesdPagename end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Parse a ==Section== from a page local function getsection(text, section) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p bpt0jhlqmggzgy9r1uern7yelmcw6fu 797235 797234 2018-06-18T15:25:45Z en>Certes 0 Remove DISPLAYTITLE and Short description in case they don't precede the lead 797235 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if t local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local isInfobox = mw.ustring.match(text, "{{%s*[Ii]nfobox") if not isInfobox then -- check by expanding template, to catch infoboxes with other names (e.g. {{subspeciesbox}}) local frame = mw.getCurrentFrame() local expandedContent = frame:preprocess(text) isInfobox = mw.ustring.match(expandedContent, "<table[^>]-infobox") or mw.ustring.match(expandedContent, "{|[^\n]*infobox") end if isInfobox then local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover", "static_image_name", "static_image", "image_skyline", "image_shield", "static_image2_name", "static_image2"}, "%s*=%s*(.*)") if image then -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = parsecaption(matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*(.*)")) if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" end end return token end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w%s]-%f[%w][Dd]isam[%w%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", "") -- remove imagemaps text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*sidebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-stub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then text, noramliesdPagename = getContent(pagename) if not noramliesdPagename then return err("No title for page name " .. pagename) else pagename = noramliesdPagename end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Parse a ==Section== from a page local function getsection(text, section) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p 64us2zk74bs09crbrowufpgdnvllmf9 797236 797235 2018-06-19T04:38:51Z en>Evad37 0 convert imagemaps into standard images 797236 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if t local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local isInfobox = mw.ustring.match(text, "{{%s*[Ii]nfobox") if not isInfobox then -- check by expanding template, to catch infoboxes with other names (e.g. {{subspeciesbox}}) local frame = mw.getCurrentFrame() local expandedContent = frame:preprocess(text) isInfobox = mw.ustring.match(expandedContent, "<table[^>]-infobox") or mw.ustring.match(expandedContent, "{|[^\n]*infobox") end if isInfobox then local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover", "static_image_name", "static_image", "image_skyline", "image_shield", "static_image2_name", "static_image2"}, "%s*=%s*(.*)") if image then -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = parsecaption(matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*(.*)")) if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" end end return token end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w%s]-%f[%w][Dd]isam[%w%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*sidebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-stub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then text, noramliesdPagename = getContent(pagename) if not noramliesdPagename then return err("No title for page name " .. pagename) else pagename = noramliesdPagename end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Parse a ==Section== from a page local function getsection(text, section) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p 87tecb3py2i7ofuhhirj5k9018320nf 797237 797236 2018-06-20T09:05:02Z en>Evad37 0 use frame:expandTemplate instead of frame:preprocess for infobox detection 797237 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if t local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local templateName = mw.ustring.match(text, "{{%s*(.-)%s*[|}]") local isInfobox = mw.ustring.match(templateName, "[Ii]nfobox") if not isInfobox then -- check by expanding template, to catch infoboxes with other names (e.g. {{subspeciesbox}}) local frame = mw.getCurrentFrame() local expandedContent = frame:expandTemplate{title = templateName} isInfobox = mw.ustring.match(expandedContent, "<table[^>]-infobox") or mw.ustring.match(expandedContent, "{|[^\n]*infobox") end if isInfobox then local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover", "static_image_name", "static_image", "image_skyline", "image_shield", "static_image2_name", "static_image2"}, "%s*=%s*(.*)") if image then -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = parsecaption(matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*(.*)")) if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" end end return token end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w%s]-%f[%w][Dd]isam[%w%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*sidebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-stub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then text, noramliesdPagename = getContent(pagename) if not noramliesdPagename then return err("No title for page name " .. pagename) else pagename = noramliesdPagename end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Parse a ==Section== from a page local function getsection(text, section) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p beh0ctkrcjm55s5s2f4aksibhlivqei 797238 797237 2018-06-20T10:19:39Z en>Certes 0 Infobox check: exclude {{MAGICWORD:arg}}: not a template 797238 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if t local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local templateName = mw.ustring.match(text, "{{%s*(.-)%s*[|}]") local isInfobox = mw.ustring.match(templateName, "[Ii]nfobox") if not isInfobox and not mw.ustring.match(templateName, "^%u+:") then -- exclude {{MAGICWORD:arg}}: not a template -- check by expanding template, to catch infoboxes with other names (e.g. {{subspeciesbox}}) local frame = mw.getCurrentFrame() local expandedContent = frame:expandTemplate{title = templateName} isInfobox = mw.ustring.match(expandedContent, "<table[^>]-infobox") or mw.ustring.match(expandedContent, "{|[^\n]*infobox") end if isInfobox then local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover", "static_image_name", "static_image", "image_skyline", "image_shield", "static_image2_name", "static_image2"}, "%s*=%s*(.*)") if image then -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = parsecaption(matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*(.*)")) if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" end end return token end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w%s]-%f[%w][Dd]isam[%w%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*ref[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*ref.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*imagemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*sidebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-stub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then text, noramliesdPagename = getContent(pagename) if not noramliesdPagename then return err("No title for page name " .. pagename) else pagename = noramliesdPagename end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Parse a ==Section== from a page local function getsection(text, section) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p jgd4id5dq4rh8wrg8m6cyd0n1ua1o3c 797239 797238 2018-06-20T10:44:59Z en>Certes 0 Match Citation templates and tag with capitals (<Ref> etc.) 797239 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if t local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local templateName = mw.ustring.match(text, "{{%s*(.-)%s*[|}]") local isInfobox = mw.ustring.match(templateName, "[Ii]nfobox") if not isInfobox and not mw.ustring.match(templateName, "^%u+:") then -- exclude {{MAGICWORD:arg}}: not a template -- check by expanding template, to catch infoboxes with other names (e.g. {{subspeciesbox}}) local frame = mw.getCurrentFrame() local expandedContent = frame:expandTemplate{title = templateName} isInfobox = mw.ustring.match(expandedContent, "<table[^>]-infobox") or mw.ustring.match(expandedContent, "{|[^\n]*infobox") end if isInfobox then local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover", "static_image_name", "static_image", "image_skyline", "image_shield", "static_image2_name", "static_image2"}, "%s*=%s*(.*)") if image then -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = parsecaption(matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*(.*)")) if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" end end return token end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[ _]+[%w%s]-", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w%s]-%f[%w][Dd]isam[%w%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then text, noramliesdPagename = getContent(pagename) if not noramliesdPagename then return err("No title for page name " .. pagename) else pagename = noramliesdPagename end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Parse a ==Section== from a page local function getsection(text, section) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p be1i13wls9b5npzakf10xpz4v40kl4j 797240 797239 2018-06-22T01:32:30Z en>Certes 0 Strip {{Coord}} and aliases 797240 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if t local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local templateName = mw.ustring.match(text, "{{%s*(.-)%s*[|}]") local isInfobox = mw.ustring.match(templateName, "[Ii]nfobox") if not isInfobox and not mw.ustring.match(templateName, "^%u+:") then -- exclude {{MAGICWORD:arg}}: not a template -- check by expanding template, to catch infoboxes with other names (e.g. {{subspeciesbox}}) local frame = mw.getCurrentFrame() local expandedContent = frame:expandTemplate{title = templateName} isInfobox = mw.ustring.match(expandedContent, "<table[^>]-infobox") or mw.ustring.match(expandedContent, "{|[^\n]*infobox") end if isInfobox then local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover", "static_image_name", "static_image", "image_skyline", "image_shield", "static_image2_name", "static_image2"}, "%s*=%s*(.*)") if image then -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = parsecaption(matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*(.*)")) if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" end end return token end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[ _]+[%w_%s]-", "[Cc]oor[%w_%s]-", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then text, noramliesdPagename = getContent(pagename) if not noramliesdPagename then return err("No title for page name " .. pagename) else pagename = noramliesdPagename end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Parse a ==Section== from a page local function getsection(text, section) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p adaywg15rmckb482yi7fkf5tlv9jqqw 797241 797240 2018-06-22T02:03:03Z en>Evad37 0 don't bother checking if the template is an infobox – preprocessing or expanding templates is expensive, and can cause unwanted side effectes. Also reduce the nesting of if blocks. 797241 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if t local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover", "static_image_name", "static_image", "image_skyline", "image_shield", "static_image2_name", "static_image2"}, "%s*=%s*(.*)") if not image then return nil end -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = parsecaption(matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*(.*)")) if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|]*)") if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" return token end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[ _]+[%w_%s]-", "[Cc]oor[%w_%s]-", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then text, noramliesdPagename = getContent(pagename) if not noramliesdPagename then return err("No title for page name " .. pagename) else pagename = noramliesdPagename end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Parse a ==Section== from a page local function getsection(text, section) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p j9kj523kl1aa3xbpsthqtlwds9lbxv4 797242 797241 2018-06-25T16:19:52Z en>Certes 0 Within infobox, terminate image parameters at newline 797242 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if t local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image local image = matchany(text, "|%s*", {"image", "PD_image", "image_flag", "Ship image", "Cover", "static_image_name", "static_image", "image_skyline", "image_shield", "static_image2_name", "static_image2"}, "%s*=%s*(.*)") if not image then return nil end -- add in relevant optional parameters: caption, alt text and image size token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = parsecaption(matchany(text, "|%s*", {"[Cc]aption", "Ship caption"}, "%s*=%s*([^\n]*)")) if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = mw.ustring.match(text, "|%s*alt%s*=%s*([^}|\n]*)") if alt then token = token .. "|alt=" .. alt end local image_size = mw.ustring.match(text, "|%s*image_size%s*=%s*([^}|\n]*)") if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" return token end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[ _]+[%w_%s]-", "[Cc]oor[%w_%s]-", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local image = argimage(token) or parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then text, noramliesdPagename = getContent(pagename) if not noramliesdPagename then return err("No title for page name " .. pagename) else pagename = noramliesdPagename end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Parse a ==Section== from a page local function getsection(text, section) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p lasxw5cwu1b919ggajxuc49o4yvim3u 797243 797242 2018-07-13T08:47:25Z en>Evad37 0 better image matching 797243 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if t local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} for image in mw.ustring.gmatch(text, "|%s*[^=|]*[Ii][Mm][Aa][Gg][Ee][^=|]*%s*=%s*(.*)") do hasImages = true local position = mw.ustring.find(text, image, 0, true) images[position] = image end for image in mw.ustring.gmatch(text, "|%s*[^=|]*[Pp][Hh][Oo][Tt][Oo][^=|]*%s*=%s*(.*)") do hasImages = true local position = mw.ustring.find(text, image, 0, true) images[position] = image end for image in mw.ustring.gmatch(text, "|%s*[^|{}]*%s*=%s*([^|{}]*%.%a%a%a%a?)%s*[|}]") do hasImages = true local position = mw.ustring.find(text, image, 0, true) if not images[position] then images[position] = image end end if not hasImages then return nil end -- find all captions local captions = {} local capture_from = 0 while capture_from < #text do local caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*([^\n]+)", capture_from) if caption then caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then local position = mw.ustring.find(text, caption, 0, true) -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = #text end end -- find all alt text local altTexts = {} for altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]*%s*=%s*([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then local position = mw.ustring.find(text, altText, 0, true) -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then local position = mw.ustring.find(text, imageSizeMatch, 0, true) -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for index, image in pairs(images) do local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[ _]+[%w_%s]-", "[Cc]oor[%w_%s]-", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then text, noramliesdPagename = getContent(pagename) if not noramliesdPagename then return err("No title for page name " .. pagename) else pagename = noramliesdPagename end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Parse a ==Section== from a page local function getsection(text, section) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p 81bezv0e3o10foubrqfryjquq8mhc38 797244 797243 2018-07-15T10:43:49Z en>Certes 0 Adding sectiononly= to exclude subsections 797244 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if t local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} for image in mw.ustring.gmatch(text, "|%s*[^=|]*[Ii][Mm][Aa][Gg][Ee][^=|]*%s*=%s*(.*)") do hasImages = true local position = mw.ustring.find(text, image, 0, true) images[position] = image end for image in mw.ustring.gmatch(text, "|%s*[^=|]*[Pp][Hh][Oo][Tt][Oo][^=|]*%s*=%s*(.*)") do hasImages = true local position = mw.ustring.find(text, image, 0, true) images[position] = image end for image in mw.ustring.gmatch(text, "|%s*[^|{}]*%s*=%s*([^|{}]*%.%a%a%a%a?)%s*[|}]") do hasImages = true local position = mw.ustring.find(text, image, 0, true) if not images[position] then images[position] = image end end if not hasImages then return nil end -- find all captions local captions = {} local capture_from = 0 while capture_from < #text do local caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*([^\n]+)", capture_from) if caption then caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then local position = mw.ustring.find(text, caption, 0, true) -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = #text end end -- find all alt text local altTexts = {} for altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]*%s*=%s*([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then local position = mw.ustring.find(text, altText, 0, true) -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then local position = mw.ustring.find(text, imageSizeMatch, 0, true) -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for index, image in pairs(images) do local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[ _]+[%w_%s]-", "[Cc]oor[%w_%s]-", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "\n%s*{{%s*[Tt][Oo][Cc].-}}", "\n") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then text, noramliesdPagename = getContent(pagename) if not noramliesdPagename then return err("No title for page name " .. pagename) else pagename = noramliesdPagename end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p dpq686ozxqkvbt1uvwgvisyi86arkg6 797245 797244 2018-07-16T03:15:17Z en>Evad37 0 TOC templates might not start on a new line 797245 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if t local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} for image in mw.ustring.gmatch(text, "|%s*[^=|]*[Ii][Mm][Aa][Gg][Ee][^=|]*%s*=%s*(.*)") do hasImages = true local position = mw.ustring.find(text, image, 0, true) images[position] = image end for image in mw.ustring.gmatch(text, "|%s*[^=|]*[Pp][Hh][Oo][Tt][Oo][^=|]*%s*=%s*(.*)") do hasImages = true local position = mw.ustring.find(text, image, 0, true) images[position] = image end for image in mw.ustring.gmatch(text, "|%s*[^|{}]*%s*=%s*([^|{}]*%.%a%a%a%a?)%s*[|}]") do hasImages = true local position = mw.ustring.find(text, image, 0, true) if not images[position] then images[position] = image end end if not hasImages then return nil end -- find all captions local captions = {} local capture_from = 0 while capture_from < #text do local caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*([^\n]+)", capture_from) if caption then caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then local position = mw.ustring.find(text, caption, 0, true) -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = #text end end -- find all alt text local altTexts = {} for altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]*%s*=%s*([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then local position = mw.ustring.find(text, altText, 0, true) -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then local position = mw.ustring.find(text, imageSizeMatch, 0, true) -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for index, image in pairs(images) do local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[ _]+[%w_%s]-", "[Cc]oor[%w_%s]-", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then text, noramliesdPagename = getContent(pagename) if not noramliesdPagename then return err("No title for page name " .. pagename) else pagename = noramliesdPagename end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p 990hrnpdcqmubpc1dkio332v83vww90 797246 797245 2018-07-16T03:48:18Z en>Evad37 0 pass through leadstart, so that bold text linking still works when files are included 797246 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if t local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} for image in mw.ustring.gmatch(text, "|%s*[^=|]*[Ii][Mm][Aa][Gg][Ee][^=|]*%s*=%s*(.*)") do hasImages = true local position = mw.ustring.find(text, image, 0, true) images[position] = image end for image in mw.ustring.gmatch(text, "|%s*[^=|]*[Pp][Hh][Oo][Tt][Oo][^=|]*%s*=%s*(.*)") do hasImages = true local position = mw.ustring.find(text, image, 0, true) images[position] = image end for image in mw.ustring.gmatch(text, "|%s*[^|{}]*%s*=%s*([^|{}]*%.%a%a%a%a?)%s*[|}]") do hasImages = true local position = mw.ustring.find(text, image, 0, true) if not images[position] then images[position] = image end end if not hasImages then return nil end -- find all captions local captions = {} local capture_from = 0 while capture_from < #text do local caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*([^\n]+)", capture_from) if caption then caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then local position = mw.ustring.find(text, caption, 0, true) -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = #text end end -- find all alt text local altTexts = {} for altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]*%s*=%s*([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then local position = mw.ustring.find(text, altText, 0, true) -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then local position = mw.ustring.find(text, imageSizeMatch, 0, true) -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for index, image in pairs(images) do local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[ _]+[%w_%s]-", "[Cc]oor[%w_%s]-", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then text, noramliesdPagename = getContent(pagename) if not noramliesdPagename then return err("No title for page name " .. pagename) else pagename = noramliesdPagename end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p tejuh4nvvajxozudgr0nsjt63uueqef 797247 797246 2018-07-16T16:00:07Z en>Certes 0 Minor fix to caption parsing 797247 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if t local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} for image in mw.ustring.gmatch(text, "|%s*[^=|]*[Ii][Mm][Aa][Gg][Ee][^=|]*%s*=%s*(.*)") do hasImages = true local position = mw.ustring.find(text, image, 0, true) images[position] = image end for image in mw.ustring.gmatch(text, "|%s*[^=|]*[Pp][Hh][Oo][Tt][Oo][^=|]*%s*=%s*(.*)") do hasImages = true local position = mw.ustring.find(text, image, 0, true) images[position] = image end for image in mw.ustring.gmatch(text, "|%s*[^|{}]*%s*=%s*([^|{}]*%.%a%a%a%a?)%s*[|}]") do hasImages = true local position = mw.ustring.find(text, image, 0, true) if not images[position] then images[position] = image end end if not hasImages then return nil end -- find all captions local captions = {} local capture_from = 0 while capture_from < #text do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = #text end end -- find all alt text local altTexts = {} for altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]*%s*=%s*([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then local position = mw.ustring.find(text, altText, 0, true) -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then local position = mw.ustring.find(text, imageSizeMatch, 0, true) -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for index, image in pairs(images) do local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[ _]+[%w_%s]-", "[Cc]oor[%w_%s]-", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end t = t .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then text, noramliesdPagename = getContent(pagename) if not noramliesdPagename then return err("No title for page name " .. pagename) else pagename = noramliesdPagename end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p gim8mhj1svv5ur1jjlfbvfa8em9zfyc 797248 797247 2018-07-16T17:11:20Z en>Certes 0 Put images first, to avoid them appearing wholly beneath the end of the text 797248 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if t local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} for image in mw.ustring.gmatch(text, "|%s*[^=|]*[Ii][Mm][Aa][Gg][Ee][^=|]*%s*=%s*(.*)") do hasImages = true local position = mw.ustring.find(text, image, 0, true) images[position] = image end for image in mw.ustring.gmatch(text, "|%s*[^=|]*[Pp][Hh][Oo][Tt][Oo][^=|]*%s*=%s*(.*)") do hasImages = true local position = mw.ustring.find(text, image, 0, true) images[position] = image end for image in mw.ustring.gmatch(text, "|%s*[^|{}]*%s*=%s*([^|{}]*%.%a%a%a%a?)%s*[|}]") do hasImages = true local position = mw.ustring.find(text, image, 0, true) if not images[position] then images[position] = image end end if not hasImages then return nil end -- find all captions local captions = {} local capture_from = 0 while capture_from < #text do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = #text end end -- find all alt text local altTexts = {} for altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]*%s*=%s*([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then local position = mw.ustring.find(text, altText, 0, true) -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then local position = mw.ustring.find(text, imageSizeMatch, 0, true) -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for index, image in pairs(images) do local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[ _]+[%w_%s]-", "[Cc]oor[%w_%s]-", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then text, noramliesdPagename = getContent(pagename) if not noramliesdPagename then return err("No title for page name " .. pagename) else pagename = noramliesdPagename end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p 1h078d8rcr2xrkg7jydmrr84n8yhinu 797249 797248 2018-07-16T23:26:56Z en>Certes 0 typos 797249 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if t local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} for image in mw.ustring.gmatch(text, "|%s*[^=|]*[Ii][Mm][Aa][Gg][Ee][^=|]*%s*=%s*(.*)") do hasImages = true local position = mw.ustring.find(text, image, 0, true) images[position] = image end for image in mw.ustring.gmatch(text, "|%s*[^=|]*[Pp][Hh][Oo][Tt][Oo][^=|]*%s*=%s*(.*)") do hasImages = true local position = mw.ustring.find(text, image, 0, true) images[position] = image end for image in mw.ustring.gmatch(text, "|%s*[^|{}]*%s*=%s*([^|{}]*%.%a%a%a%a?)%s*[|}]") do hasImages = true local position = mw.ustring.find(text, image, 0, true) if not images[position] then images[position] = image end end if not hasImages then return nil end -- find all captions local captions = {} local capture_from = 0 while capture_from < #text do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = #text end end -- find all alt text local altTexts = {} for altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]*%s*=%s*([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then local position = mw.ustring.find(text, altText, 0, true) -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then local position = mw.ustring.find(text, imageSizeMatch, 0, true) -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for index, image in pairs(images) do local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[ _]+[%w_%s]-", "[Cc]oor[%w_%s]-", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|#]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|#\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p lzm3qko7gd0atftj2js7rj0s3p74syf 797250 797249 2018-07-16T23:58:41Z en>Certes 0 Accept Page#Section syntax 797250 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} for image in mw.ustring.gmatch(text, "|%s*[^=|]*[Ii][Mm][Aa][Gg][Ee][^=|]*%s*=%s*(.*)") do hasImages = true local position = mw.ustring.find(text, image, 0, true) images[position] = image end for image in mw.ustring.gmatch(text, "|%s*[^=|]*[Pp][Hh][Oo][Tt][Oo][^=|]*%s*=%s*(.*)") do hasImages = true local position = mw.ustring.find(text, image, 0, true) images[position] = image end for image in mw.ustring.gmatch(text, "|%s*[^|{}]*%s*=%s*([^|{}]*%.%a%a%a%a?)%s*[|}]") do hasImages = true local position = mw.ustring.find(text, image, 0, true) if not images[position] then images[position] = image end end if not hasImages then return nil end -- find all captions local captions = {} local capture_from = 0 while capture_from < #text do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = #text end end -- find all alt text local altTexts = {} for altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]*%s*=%s*([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then local position = mw.ustring.find(text, altText, 0, true) -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then local position = mw.ustring.find(text, imageSizeMatch, 0, true) -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for index, image in pairs(images) do local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[ _]+[%w_%s]-", "[Cc]oor[%w_%s]-", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p 8f4vhges9w4cqzzvdcfnx5k340sjzmb 797251 797250 2018-07-19T06:36:12Z en>Evad37 0 update from sandbox: avoid "pattern over 10,000 characters" errors 797251 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} for position, image in mw.ustring.gmatch(text, "|%s*[^=|]*[Ii][Mm][Aa][Gg][Ee][^=|]*%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|]*[Pp][Hh][Oo][Tt][Oo][^=|]*%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^|{}]*%s*=%s*()([^|{}]*%.%a%a%a%a?)%s*[|}]") do hasImages = true if not images[position] then images[position] = image end end if not hasImages then return nil end -- find all captions local captions = {} local capture_from = 0 while capture_from < #text do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = #text end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]*%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for index, image in pairs(images) do local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[ _]+[%w_%s]-", "[Cc]oor[%w_%s]-", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p dbenvziaj0e1iz88je6wa84xx80zacq 797252 797251 2018-07-19T07:35:46Z en>Evad37 0 match more images; ensure images are sorted in the order that they appear in the wikitext 797252 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = mRedirect.getTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]") do hasImages = true if not images[position] then images[position] = image end end if not hasImages then return nil end -- find all captions local captions = {} local capture_from = 0 while capture_from < #text do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = #text end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]*%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[ _]+[%w_%s]-", "[Cc]oor[%w_%s]-", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p qskj4zjlof84fr274fz2skd6f8po01i 797253 797252 2018-07-21T05:16:15Z en>Evad37 0 Get a redirect target without using the expensive title object property .isRedirect; remove TOC behavior switches from excerpts 797253 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]") do hasImages = true if not images[position] then images[position] = image end end if not hasImages then return nil end -- find all captions local captions = {} local capture_from = 0 while capture_from < #text do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = #text end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]*%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[ _]+[%w_%s]-", "[Cc]oor[%w_%s]-", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__%s*", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p mg2dtjw1z1rvid6vz7aja1e47jb7ajb 797254 797253 2018-07-25T04:09:03Z en>Evad37 0 nostubs option to avoid picking stubs; don't remove trailing whitespace/newlines when removing TOC behavior switches 797254 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]") do hasImages = true if not images[position] then images[position] = image end end if not hasImages then return nil end -- find all captions local captions = {} local capture_from = 0 while capture_from < #text do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = #text end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]*%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[ _]+[%w_%s]-", "[Cc]oor[%w_%s]-", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p qtdf780j24y1w7imw6fxhsqbckl4wzy 797255 797254 2018-08-26T21:14:10Z en>Certes 0 Remove "unreliable source?" tags 797255 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]") do hasImages = true if not images[position] then images[position] = image end end if not hasImages then return nil end -- find all captions local captions = {} local capture_from = 0 while capture_from < #text do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = #text end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]*%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[ _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if options.fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. options.fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p s7cqsqmu110zbem643qp92odlaajkwa 797256 797255 2018-08-28T07:02:56Z en>Evad37 0 prevent empty or whitespace-only fileargs being used 797256 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]") do hasImages = true if not images[position] then images[position] = image end end if not hasImages then return nil end -- find all captions local captions = {} local capture_from = 0 while capture_from < #text do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = #text end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]*%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu]a", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[ _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p pyup5ytom9muxfmfeeztbiwvuxw4fmy 797257 797256 2018-09-02T04:05:39Z en>Evad37 0 catch {{efn}} variants for roman and greek 797257 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]") do hasImages = true if not images[position] then images[position] = image end end if not hasImages then return nil end -- find all captions local captions = {} local capture_from = 0 while capture_from < #text do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = #text end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]*%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[ _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p ko7f676ze3gzprjg6s3zeof14sgmlao 797258 797257 2018-09-02T21:00:09Z en>Certes 0 Remove {{cite-web}} etc. If a wanted template has unwanted nested templates, purge them too. Example: {{efn-lr... within {{nihongo... in [[Matcha]]. 797258 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]") do hasImages = true if not images[position] then images[position] = image end end if not hasImages then return nil end -- find all captions local captions = {} local capture_from = 0 while capture_from < #text do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = #text end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]*%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(t, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p dqp7dtj6o9zbaznp2qxz22jimx8fdht 797259 797258 2018-09-03T08:29:56Z en>Evad37 0 fix altText pattern, to avoid bogus file options lint errors 797259 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]") do hasImages = true if not images[position] then images[position] = image end end if not hasImages then return nil end -- find all captions local captions = {} local capture_from = 0 while capture_from < #text do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = #text end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(t, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p o9x4avpy3zs1yaccn7zy1rvfavqqn5r 797260 797259 2018-09-13T10:35:09Z en>Evad37 0 replace annotated links with real links before looking for links or list items 797260 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]") do hasImages = true if not images[position] then images[position] = image end end if not hasImages then return nil end -- find all captions local captions = {} local capture_from = 0 while capture_from < #text do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = #text end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", -- aliases for Primary source inline "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]econdary[ _]+source[ _]+needed", "[Pp]rimary[ _]+source[ _]+claim", "[Pp]rimary[%- _]+source[%- _]+inline", "[Pp]rimary[%- _]+inline", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(t, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p mv7z59fsyvccbd19xdm51f9fm6mgou1 797261 797260 2018-09-13T10:49:27Z en>Certes 0 Remove more primary source etc. tags 797261 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]") do hasImages = true if not images[position] then images[position] = image end end if not hasImages then return nil end -- find all captions local captions = {} local capture_from = 0 while capture_from < #text do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = #text end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf-published[%w_%s]-", "[Uu]ser-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(t, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p bxjt7r5ofemyoye16op1zp9ebtnkytj 797262 797261 2018-09-13T13:03:13Z en>Certes 0 Escape literal hyphens 797262 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]") do hasImages = true if not images[position] then images[position] = image end end if not hasImages then return nil end -- find all captions local captions = {} local capture_from = 0 while capture_from < #text do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = #text end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(t, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p 48dr9qmde7ecbm9gqwmtzyb6u14ll4k 797263 797262 2018-09-15T20:45:25Z en>Certes 0 Remove {{Failed verification}} and its multitudinous aliases 797263 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]") do hasImages = true if not images[position] then images[position] = image end end if not hasImages then return nil end -- find all captions local captions = {} local capture_from = 0 while capture_from < #text do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = #text end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(t, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p 7tgjxhqbswr17lbdkp5yqzkekv4gq22 797264 797263 2018-09-22T23:22:07Z en>Certes 0 Remove Template:When 797264 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]") do hasImages = true if not images[position] then images[position] = image end end if not hasImages then return nil end -- find all captions local captions = {} local capture_from = 0 while capture_from < #text do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = #text end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(t, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text if not filesOnly then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p 1ayit5u16jf46kngpqqh26ck624z2bn 797265 797264 2018-09-25T10:14:44Z en>Certes 0 Remove a line which is just a template: typically used for navboxes in footer etc. 797265 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]") do hasImages = true if not images[position] then images[position] = image end end if not hasImages then return nil end -- find all captions local captions = {} local capture_from = 0 while capture_from < #text do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = #text end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(t, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p bkw1cjd883la6avgtij8htgdlwcauyn 797266 797265 2018-10-04T21:00:00Z en>Certes 0 Fix bug: when a line contains both nested templates and template with a |ref parameter, e.g. [[Somalia]], remove both 797266 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]") do hasImages = true if not images[position] then images[position] = image end end if not hasImages then return nil end -- find all captions local captions = {} local capture_from = 0 while capture_from < #text do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = #text end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p t2k1k5inin0cm8r0luniembees5i3x6 797267 797266 2018-10-09T11:59:43Z en>Certes 0 Extend caption to include a nested template flowed over multiple lines. (Function parsecaption() strips the template out later.) 797267 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]") do hasImages = true if not images[position] then images[position] = image end end if not hasImages then return nil end -- find all captions local captions = {} local capture_from = 0 while capture_from < #text do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = #text end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p 2ds7s82gzl0lzo1cqvge768vz4vn0pc 797268 797267 2018-10-09T12:13:53Z en>Certes 0 remove DIY hatnote indented with a colon 797268 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]") do hasImages = true if not images[position] then images[position] = image end end if not hasImages then return nil end -- find all captions local captions = {} local capture_from = 0 while capture_from < #text do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = #text end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p 9mw9z15mh4qud0sdiq6xobz6t1qieky 797269 797268 2018-11-05T11:24:48Z en>Certes 0 Extract a section when processing a redirect to Page#Section. (Pending change in sandbox not promoted yet.) 797269 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]") do hasImages = true if not images[position] then images[position] = image end end if not hasImages then return nil end -- find all captions local captions = {} local capture_from = 0 while capture_from < #text do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = #text end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??"} if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p lczekfhnal5crv8m98oo66n7r2o80sx 797270 797269 2018-11-18T22:43:58Z en>Certes 0 Remove {{Update}} and chums 797270 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)") do hasImages = true images[position] = image end for position, image in mw.ustring.gmatch(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]") do hasImages = true if not images[position] then images[position] = image end end if not hasImages then return nil end -- find all captions local captions = {} local capture_from = 0 while capture_from < #text do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = #text end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p l9byrihl69nuh97cqq0s7prrj6x5oys 797271 797270 2018-12-06T18:12:42Z en>Certes 0 Copy Morpeth imagemap fix (mostly by [[User:Evad37]]) from sandbox 797271 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p ly8931mxto2ur57gzg7d217usav64y9 797272 797271 2018-12-22T19:38:12Z en>Ymblanter 0 Protected "[[Module:Excerpt]]": [[WP:High-risk templates|High-risk Lua module]]: request at [[WP:RFPP]] ([Edit=Require autoconfirmed or confirmed access] (indefinite)) 797271 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr]ef[^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr]ef.->.-<%s*/%s*ref%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p ly8931mxto2ur57gzg7d217usav64y9 797273 797272 2018-12-26T00:01:36Z en>Dreamy Jazz 0 merge from sandbox; handle strange capitalisation of ref tags, which are valid but were not removed (and in some cases causing spew errors) 797273 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<noinclude>.-</noinclude>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii]magemap.->.-<%s*/%s*imagemap%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p dm6hbt71k0b8rmsyjcdrptyufa0hm15 797274 797273 2018-12-26T00:07:39Z en>Dreamy Jazz 0 merge from sandbox; do the same regex fixes for noinclude and imagemap (just in case) 797274 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p glmn4zfwhelcn8y5qaf2h9ajpvgifdc 797275 797274 2018-12-26T00:40:32Z en>Dreamy Jazz 0 merge from sandbox; remove [[Template:By whom]] and aliases 797275 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for By Whom "[Bb]y[ _][Ww]ho%??","[Bb]y[ _][Ww]hom%??" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p lf8haslabg4flscrmue8rrq2dl6e7s8 797276 797275 2018-12-26T00:42:30Z en>Dreamy Jazz 0 handle [[Template:Citation-needed]] 797276 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for By Whom "[Bb]y[ _][Ww]ho%??","[Bb]y[ _][Ww]hom%??" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p g2cq8q20exbj8qozmtu22dx7xnw7snv 797277 797276 2018-12-26T12:28:05Z en>Certes 0 Remove ancestry charts 797277 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for By Whom "[Bb]y[ _][Ww]ho%??","[Bb]y[ _][Ww]hom%??", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p ae0iskfh73tjr3oi5ngtjoxhnag9fwm 797278 797277 2018-12-26T20:24:26Z en>Dreamy Jazz 0 add aliases 797278 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p n2zq2u5aam9vj53mfptvey9povk7lay 797279 797278 2018-12-27T03:16:58Z en>Evad37 0 make sure hatnotes aren't mistaken for the start of the lead 797279 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p 735hjyp373oyo59o38fgag6nkcxr4fg 797280 797279 2019-01-07T01:58:32Z en>Certes 0 <HTML tag>{{template}} now counts as a template on a line of its own, fixes [[Indo-Pakistani wars and conflicts]] 797280 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p t01uomi7axv9fj7lcb6cjemf3789y7s 797281 797280 2019-01-08T12:44:24Z en>Certes 0 Restore line removed accidentally in previous edit 797281 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p e5xom6x7wmp5h6fngt8h23y87p1d266 797282 797281 2019-01-09T19:01:07Z en>Certes 0 Remove unmatched {{ etc. which might break an enclosing template such as {{#tag:gallery}} 797282 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "{\27%1\27}"); -- {{sometemplate}} → {E{sometemplate}E} where E represents escape until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "[\27%1\27]"); until text == t text = text.gsub(text, "([{}%[%]])%1.*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: {E{ → {{, ]E] → ]], etc. if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p g3wtvdw29h20lcm6omdxjl0ysej8ny0 797283 797282 2019-01-09T19:25:46Z en>Certes 0 Bug fix: avoid truncating after ]]]] because second and third brackets don't form a pair 797283 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "{\27%1\27}"); -- {{sometemplate}} → {E{sometemplate}E} where E represents escape until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "[\27%1\27]"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]]E] etc. text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: {E{ → {{, ]E] → ]], etc. if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p agl2mfzkjzk89o6jo6u03h25rs161xc 797284 797283 2019-01-10T12:09:08Z en>Certes 0 More escape characters to deal with text like [comment [[wikilink]]]. Exclude }} within <math> to disregard cases like <math>\sqrt{\hat{x}}</math> (test case: [[Milankovitch cycles]]). 797284 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math *>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]]E] etc. text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p q4udkxcjiyb0fhox9fttbe80yaxpksu 797285 797284 2019-01-10T12:18:32Z en>Certes 0 Handle <math foo="bar"> like <math>; test case [[Commensurability (mathematics)]] 797285 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]]E] etc. text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p 3sih6cyvvizhbf3ek4z8xjd3vestz5e 797286 797285 2019-01-10T21:03:00Z en>Certes 0 Exclude following | or }} from alt text, after accepting embedded {{foo|bar}} etc. Test case: [[Quickplay Media]] on [[Portal:AT&T]] 797286 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.find(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.find(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.find(altText, "()}}", lookfrom) or len+1, mw.ustring.find(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]]E] etc. text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p hceo6tm56vb69znfpjoxk0ubi219yn0 797287 797286 2019-01-11T20:17:25Z en>Dreamy Jazz 0 merge from sandbox; add Cref and others 797287 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.find(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.find(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.find(altText, "()}}", lookfrom) or len+1, mw.ustring.find(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]]E] etc. text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p 3zfhpgxduiqj6yb96izhsr9eige0mk6 797288 797287 2019-01-12T09:16:30Z en>Dreamy Jazz 0 add IPA needed 797288 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.find(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.find(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.find(altText, "()}}", lookfrom) or len+1, mw.ustring.find(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]]E] etc. text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p hxernaj8elwqkd4dxzsyn50agy5vgpu 797289 797288 2019-01-12T11:21:46Z en>Certes 0 Use match instead of find, to capture the current string position with () 797289 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]]E] etc. text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p cuf52x44feibc6c776559mvw9px910q 797290 797289 2019-01-12T11:28:37Z en>Certes 0 Remove stray }} etc. at end of text 797290 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", ""); -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p 6xqzpn82c6uju2d1fiwm29mcmbajj7u 797291 797290 2019-01-12T11:32:58Z en>Certes 0 More unwanted templates 797291 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", ""); -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p cvlqlrng7ldx3irsgcczlpyamiqhta3 797292 797291 2019-01-15T11:45:00Z en>Certes 0 remove musical scores 797292 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", ""); -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p df59z5pnx0yhok2mdotogau9vhagsm0 797293 797292 2019-01-21T10:34:09Z en>Dreamy Jazz 0 add [[Template:Wikisource-multi]] 797293 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. section .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", ""); -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p f239adqovbzhmy3ototdpr73a1b6xvb 797294 797293 2019-01-25T10:46:01Z en>Certes 0 Handle section names containing URL-escaped characters and characters special to Lua patterns 797294 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", ""); -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p fy7ojhr1cznxg68dnih454xpvrclw38 797295 797294 2019-02-04T12:23:18Z en>Certes 0 Replace {{audio}} by its text parameter 797295 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", ""); -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p 249hecg3jad3phz9fua2kvj8v0qscjt 797296 797295 2019-02-05T12:01:20Z en>Dreamy Jazz 0 remove math, chem and chem math 797296 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*(?:[Mm][Aa][Tt][Hh].->.-<%s*/%s*[Mm][Aa][Tt][Hh]%s*>", "") -- remove math text = mw.ustring.gsub(text, "<%s*(?:[Cc][Hh][Ee][Mm].->.-<%s*/%s*[Cc][Hh][Ee][Mm]%s*>", "") -- remove chem text = mw.ustring.gsub(text, "<%s*(?:[Cc][Hh][Ee][Mm][ ]*[Mm][Aa][Tt][Hh].->.-<%s*/%s*[Cc][Hh][Ee][Mm][ ]*[Mm][Aa][Tt][Hh]%s*>", "") -- remove chem math text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", ""); -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p heco54v0mdgls4phd8xl0ehntq69wow 797297 797296 2019-02-05T12:01:40Z en>Dreamy Jazz 0 Undid revision 881879687 by [[Special:Contributions/Dreamy Jazz|Dreamy Jazz]] ([[User talk:Dreamy Jazz|talk]]) 797297 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", ""); -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p 249hecg3jad3phz9fua2kvj8v0qscjt 797298 797297 2019-02-05T12:09:07Z en>Dreamy Jazz 0 add explain and others 797298 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note", -- Explain and others "[Ee]xplain", "[Ff]urther[ ]*explanation[ ]*needed", "[Ee]laboration[ ]*needed", "[Ee]xplanation[ ]*needed" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", ""); -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p pjkgvgxww9zhuashe3m3xaorkwinboh 797299 797298 2019-02-05T12:18:04Z en>Dreamy Jazz 0 remove TOC templates 797299 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note", -- Explain and others "[Ee]xplain", "[Ff]urther[ ]*explanation[ ]*needed", "[Ee]laboration[ ]*needed", "[Ee]xplanation[ ]*needed", -- TOC templates "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Tt][Oo][Cc][8]*[5]*", "[Tt][Oo][Cc]", "09[Aa][Zz]", "[Tt][Oo][Cc][ ]*[Cc][Oo][Mm][Pp][Aa][Cc][Tt]", "[Tt][Oo][Cc][ ]*[Ss][Mm][Aa][Ll][Ll]", "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Aa][Ll][Pp][Hh][Aa][Bb][Ee][Tt][Ii][Cc][ _]*[Tt][Oo][Cc]" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", ""); -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p ceynhjtpgbv9fmxjwfjqkzrm5xmuerw 797300 797299 2019-02-08T12:30:45Z en>Dreamy Jazz 0 add [[Template:Inflation/fn]] 797300 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", "[Ii]nflation[ _/-]*[Ff]n", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note", -- Explain and others "[Ee]xplain", "[Ff]urther[ ]*explanation[ ]*needed", "[Ee]laboration[ ]*needed", "[Ee]xplanation[ ]*needed", -- TOC templates "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Tt][Oo][Cc][8]*[5]*", "[Tt][Oo][Cc]", "09[Aa][Zz]", "[Tt][Oo][Cc][ ]*[Cc][Oo][Mm][Pp][Aa][Cc][Tt]", "[Tt][Oo][Cc][ ]*[Ss][Mm][Aa][Ll][Ll]", "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Aa][Ll][Pp][Hh][Aa][Bb][Ee][Tt][Ii][Cc][ _]*[Tt][Oo][Cc]" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "%c%s*==.*","") -- remove first ==Heading== and everything after it end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", ""); -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p bxhbm7pdoko8j6rarddvttnielx5f8u 797301 797300 2019-02-10T14:44:59Z en>Certes 0 Remove sections even if lead is blank 797301 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", "[Ii]nflation[ _/-]*[Ff]n", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note", -- Explain and others "[Ee]xplain", "[Ff]urther[ ]*explanation[ ]*needed", "[Ee]laboration[ ]*needed", "[Ee]xplanation[ ]*needed", -- TOC templates "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Tt][Oo][Cc][8]*[5]*", "[Tt][Oo][Cc]", "09[Aa][Zz]", "[Tt][Oo][Cc][ ]*[Cc][Oo][Mm][Pp][Aa][Cc][Tt]", "[Tt][Oo][Cc][ ]*[Ss][Mm][Aa][Ll][Ll]", "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Aa][Ll][Pp][Hh][Aa][Bb][Ee][Tt][Ii][Cc][ _]*[Tt][Oo][Cc]" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if it's the start of the article (blank lead) end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", ""); -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p r1c4x8en92mslf0r108yssdgpgw6if3 797302 797301 2019-02-11T16:03:02Z en>Certes 0 Treat cases of Nihongo foot 797302 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", "[Ii]nflation[ _/-]*[Ff]n", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note", -- Explain and others "[Ee]xplain", "[Ff]urther[ ]*explanation[ ]*needed", "[Ee]laboration[ ]*needed", "[Ee]xplanation[ ]*needed", -- TOC templates "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Tt][Oo][Cc][8]*[5]*", "[Tt][Oo][Cc]", "09[Aa][Zz]", "[Tt][Oo][Cc][ ]*[Cc][Oo][Mm][Pp][Aa][Cc][Tt]", "[Tt][Oo][Cc][ ]*[Ss][Mm][Aa][Ll][Ll]", "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Aa][Ll][Pp][Hh][Aa][Bb][Ee][Tt][Ii][Cc][ _]*[Tt][Oo][Cc]" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noref = mw.ustring.gsub(noref, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if it's the start of the article (blank lead) end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", ""); -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p hqis0qtljxluykpilgbpss3e56oa0hj 797303 797302 2019-02-24T13:52:25Z en>Dreamy Jazz 0 add [[Template:Biblesource]] 797303 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end if title.namespace == 6 then frame = frame or mw.getCurrentFrame() return frame:preprocess("{{" .. title.prefixedText .. "}}"), redir or title.prefixedText else return title:getContent(), redir or title.prefixedText end end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc = getContent(page) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Help gsub to remove unwanted templates local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) local unwanted = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", "[Ii]nflation[ _/-]*[Ff]n", "[Bb]iblesource", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note", -- Explain and others "[Ee]xplain", "[Ff]urther[ ]*explanation[ ]*needed", "[Ee]laboration[ ]*needed", "[Ee]xplanation[ ]*needed", -- TOC templates "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Tt][Oo][Cc][8]*[5]*", "[Tt][Oo][Cc]", "09[Aa][Zz]", "[Tt][Oo][Cc][ ]*[Cc][Oo][Mm][Pp][Aa][Cc][Tt]", "[Tt][Oo][Cc][ ]*[Ss][Mm][Aa][Ll][Ll]", "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Aa][Ll][Pp][Hh][Aa][Bb][Ee][Tt][Ii][Cc][ _]*[Tt][Oo][Cc]" } if matchany(t, "^{{%s*", unwanted, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noref = mw.ustring.gsub(noref, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if it's the start of the article (blank lead) end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", ""); -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p qmfuor3xxi8trgp3197gaiy0gqv1z5p 797304 797303 2019-02-24T21:00:21Z en>Certes 0 Prevent side-effects of frame:preprocess on image descriptions, such as setting DEFAULTSORT 797304 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Initialise this big table once to save time local unwantedTemplates = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", "[Ii]nflation[ _/-]*[Ff]n", "[Bb]iblesource", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note", -- Explain and others "[Ee]xplain", "[Ff]urther[ ]*explanation[ ]*needed", "[Ee]laboration[ ]*needed", "[Ee]xplanation[ ]*needed", -- TOC templates "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Tt][Oo][Cc][8]*[5]*", "[Tt][Oo][Cc]", "09[Aa][Zz]", "[Tt][Oo][Cc][ ]*[Cc][Oo][Mm][Pp][Aa][Cc][Tt]", "[Tt][Oo][Cc][ ]*[Ss][Mm][Aa][Ll][Ll]", "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Aa][Ll][Pp][Hh][Aa][Bb][Ee][Tt][Ii][Cc][ _]*[Tt][Oo][Cc]", "DEFAULTSORT:.-" } -- Help gsub to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchany(t, "^{{%s*", unwantedTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noref = mw.ustring.gsub(noref, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end return title:getContent(), redir or title.prefixedText end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc, rtitle = getContent(page) -- get file description and title after following any redirect if desc and desc ~= "" then -- found description on local wiki if mw.ustring.match(desc, "[Nn]on%-free") then return false end desc = mw.ustring.gsub(desc, "%b{}", striptemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess else -- try commons desc = "{{" .. rtitle .. "}}" end frame = frame or mw.getCurrentFrame() desc = frame:preprocess(desc) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(filetext .. t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return text, leadstart end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if it's the start of the article (blank lead) end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) text, leadstart = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "(.-''')(.-'*)'''", function(a, b) if mw.ustring.len(a) < 100 + (leadstart or 0) and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return a .. "[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]] else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", ""); -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p emveftegn841f82etlkttvc22z4qu69 797305 797304 2019-02-25T10:56:40Z en>Certes 0 Ignore File: text when linking bold title 797305 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Initialise this big table once to save time local unwantedTemplates = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", "[Ii]nflation[ _/-]*[Ff]n", "[Bb]iblesource", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note", -- Explain and others "[Ee]xplain", "[Ff]urther[ ]*explanation[ ]*needed", "[Ee]laboration[ ]*needed", "[Ee]xplanation[ ]*needed", -- TOC templates "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Tt][Oo][Cc][8]*[5]*", "[Tt][Oo][Cc]", "09[Aa][Zz]", "[Tt][Oo][Cc][ ]*[Cc][Oo][Mm][Pp][Aa][Cc][Tt]", "[Tt][Oo][Cc][ ]*[Ss][Mm][Aa][Ll][Ll]", "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Aa][Ll][Pp][Hh][Aa][Bb][Ee][Tt][Ii][Cc][ _]*[Tt][Oo][Cc]", "DEFAULTSORT:.-" } -- Help gsub to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchany(t, "^{{%s*", unwantedTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noref = mw.ustring.gsub(noref, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end return title:getContent(), redir or title.prefixedText end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc, rtitle = getContent(page) -- get file description and title after following any redirect if desc and desc ~= "" then -- found description on local wiki if mw.ustring.match(desc, "[Nn]on%-free") then return false end desc = mw.ustring.gsub(desc, "%b{}", striptemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess else -- try commons desc = "{{" .. rtitle .. "}}" end frame = frame or mw.getCurrentFrame() desc = frame:preprocess(desc) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return filetext, text end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if it's the start of the article (blank lead) end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) local filetext filetext, text = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "()'''(.-'*)'''", function(a, b) if a < 100 and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return "'''[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]]''' else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end text = filetext .. text -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", ""); -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p c0s9fys6d7e3fzc5b5azpx4rjefty2b 797306 797305 2019-02-25T12:43:07Z en>Certes 0 Bug fix: check rtitle is truthy to fix [[Portal:Nike, Inc.]] 797306 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Initialise this big table once to save time local unwantedTemplates = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", "[Ii]nflation[ _/-]*[Ff]n", "[Bb]iblesource", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note", -- Explain and others "[Ee]xplain", "[Ff]urther[ ]*explanation[ ]*needed", "[Ee]laboration[ ]*needed", "[Ee]xplanation[ ]*needed", -- TOC templates "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Tt][Oo][Cc][8]*[5]*", "[Tt][Oo][Cc]", "09[Aa][Zz]", "[Tt][Oo][Cc][ ]*[Cc][Oo][Mm][Pp][Aa][Cc][Tt]", "[Tt][Oo][Cc][ ]*[Ss][Mm][Aa][Ll][Ll]", "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Aa][Ll][Pp][Hh][Aa][Bb][Ee][Tt][Ii][Cc][ _]*[Tt][Oo][Cc]", "DEFAULTSORT:.-" } -- Help gsub to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchany(t, "^{{%s*", unwantedTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noref = mw.ustring.gsub(noref, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end return title:getContent(), redir or title.prefixedText end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc, rtitle = getContent(page) -- get file description and title after following any redirect if desc and desc ~= "" then -- found description on local wiki if mw.ustring.match(desc, "[Nn]on%-free") then return false end desc = mw.ustring.gsub(desc, "%b{}", striptemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess elseif not rtitle then return false else -- try commons desc = "{{" .. rtitle .. "}}" end frame = frame or mw.getCurrentFrame() desc = frame:preprocess(desc) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return filetext, text end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if it's the start of the article (blank lead) end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) local filetext filetext, text = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "()'''(.-'*)'''", function(a, b) if a < 100 and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return "'''[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]]''' else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end text = filetext .. text -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", ""); -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p psukx2nqv37g9lo0x610jtwhs9qdo05 797307 797306 2019-02-25T13:12:39Z en>Certes 0 Ensure div tags match 797307 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Initialise this big table once to save time local unwantedTemplates = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", "[Ii]nflation[ _/-]*[Ff]n", "[Bb]iblesource", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note", -- Explain and others "[Ee]xplain", "[Ff]urther[ ]*explanation[ ]*needed", "[Ee]laboration[ ]*needed", "[Ee]xplanation[ ]*needed", -- TOC templates "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Tt][Oo][Cc][8]*[5]*", "[Tt][Oo][Cc]", "09[Aa][Zz]", "[Tt][Oo][Cc][ ]*[Cc][Oo][Mm][Pp][Aa][Cc][Tt]", "[Tt][Oo][Cc][ ]*[Ss][Mm][Aa][Ll][Ll]", "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Aa][Ll][Pp][Hh][Aa][Bb][Ee][Tt][Ii][Cc][ _]*[Tt][Oo][Cc]", "DEFAULTSORT:.-" } -- Help gsub to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchany(t, "^{{%s*", unwantedTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noref = mw.ustring.gsub(noref, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end return title:getContent(), redir or title.prefixedText end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc, rtitle = getContent(page) -- get file description and title after following any redirect if desc and desc ~= "" then -- found description on local wiki if mw.ustring.match(desc, "[Nn]on%-free") then return false end desc = mw.ustring.gsub(desc, "%b{}", striptemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess elseif not rtitle then return false else -- try commons desc = "{{" .. rtitle .. "}}" end frame = frame or mw.getCurrentFrame() desc = frame:preprocess(desc) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return filetext, text end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if it's the start of the article (blank lead) end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Remove unmatched <tag> or </tag> tags local function fixtags(text, tag) local startcount = 0 for i in mw.ustring.gmatch(text, "<%s*" .. tag .. "%f[^%w_].->") do startcount = startcount + 1 end local endcount = 0 for i in mw.ustring.gmatch(text, "<%s*/" .. tag .. "%f[^%w_].->") do endcount = endcount + 1 end if startcount > endcount then -- more <tag> than </tag>: remove the last few <tag>s local i = 0 text = mw.ustring.gsub(text, "<%s*" .. tag .. "%f[^%w_].->", function(t) i = i + 1 if i > endcount then return "" else return nil end end) -- "end" here terminates the anonymous replacement function(t) passed to gsub elseif endcount > startcount then -- more </tag> than <tag>: remove the first few </tag>s text = mw.ustring.gsub(text, "<%s*/" .. tag .. "%f[^%w_].->", "", endcount - startcount) end return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) local filetext filetext, text = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "()'''(.-'*)'''", function(a, b) if a < 100 and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return "'''[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]]''' else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end text = filetext .. text -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", ""); -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. -- Ensure div tags match text = fixtags(text, "div") if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p 47oxrgxqot0hg3v7h91kgtc3u216x7i 797308 797307 2019-03-22T19:01:58Z en>Certes 0 Remove another unwanted hat template 797308 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Initialise this big table once to save time local unwantedTemplates = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", "[Ii]nflation[ _/-]*[Ff]n", "[Bb]iblesource", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note", -- Explain and others "[Ee]xplain", "[Ff]urther[ ]*explanation[ ]*needed", "[Ee]laboration[ ]*needed", "[Ee]xplanation[ ]*needed", -- TOC templates "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Tt][Oo][Cc][8]*[5]*", "[Tt][Oo][Cc]", "09[Aa][Zz]", "[Tt][Oo][Cc][ ]*[Cc][Oo][Mm][Pp][Aa][Cc][Tt]", "[Tt][Oo][Cc][ ]*[Ss][Mm][Aa][Ll][Ll]", "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Aa][Ll][Pp][Hh][Aa][Bb][Ee][Tt][Ii][Cc][ _]*[Tt][Oo][Cc]", "DEFAULTSORT:.-", "[Oo]ne[ _]+source" } -- Help gsub to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchany(t, "^{{%s*", unwantedTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noref = mw.ustring.gsub(noref, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end return title:getContent(), redir or title.prefixedText end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc, rtitle = getContent(page) -- get file description and title after following any redirect if desc and desc ~= "" then -- found description on local wiki if mw.ustring.match(desc, "[Nn]on%-free") then return false end desc = mw.ustring.gsub(desc, "%b{}", striptemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess elseif not rtitle then return false else -- try commons desc = "{{" .. rtitle .. "}}" end frame = frame or mw.getCurrentFrame() desc = frame:preprocess(desc) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return filetext, text end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if it's the start of the article (blank lead) end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Remove unmatched <tag> or </tag> tags local function fixtags(text, tag) local startcount = 0 for i in mw.ustring.gmatch(text, "<%s*" .. tag .. "%f[^%w_].->") do startcount = startcount + 1 end local endcount = 0 for i in mw.ustring.gmatch(text, "<%s*/" .. tag .. "%f[^%w_].->") do endcount = endcount + 1 end if startcount > endcount then -- more <tag> than </tag>: remove the last few <tag>s local i = 0 text = mw.ustring.gsub(text, "<%s*" .. tag .. "%f[^%w_].->", function(t) i = i + 1 if i > endcount then return "" else return nil end end) -- "end" here terminates the anonymous replacement function(t) passed to gsub elseif endcount > startcount then -- more </tag> than <tag>: remove the first few </tag>s text = mw.ustring.gsub(text, "<%s*/" .. tag .. "%f[^%w_].->", "", endcount - startcount) end return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) local filetext filetext, text = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "()'''(.-'*)'''", function(a, b) if a < 100 and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return "'''[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]]''' else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end text = filetext .. text -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", ""); -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. -- Ensure div tags match text = fixtags(text, "div") if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p i8xn74fbpcl11sriapdu6aak9xsfjmr 797309 797308 2019-05-24T00:09:03Z en>Certes 0 If excerpt has onlyinclude sections, remove those tags and the text outside them 797309 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Initialise this big table once to save time local unwantedTemplates = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", "[Ii]nflation[ _/-]*[Ff]n", "[Bb]iblesource", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note", -- Explain and others "[Ee]xplain", "[Ff]urther[ ]*explanation[ ]*needed", "[Ee]laboration[ ]*needed", "[Ee]xplanation[ ]*needed", -- TOC templates "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Tt][Oo][Cc][8]*[5]*", "[Tt][Oo][Cc]", "09[Aa][Zz]", "[Tt][Oo][Cc][ ]*[Cc][Oo][Mm][Pp][Aa][Cc][Tt]", "[Tt][Oo][Cc][ ]*[Ss][Mm][Aa][Ll][Ll]", "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Aa][Ll][Pp][Hh][Aa][Bb][Ee][Tt][Ii][Cc][ _]*[Tt][Oo][Cc]", "DEFAULTSORT:.-", "[Oo]ne[ _]+source" } -- Help gsub to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchany(t, "^{{%s*", unwantedTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noref = mw.ustring.gsub(noref, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end return title:getContent(), redir or title.prefixedText end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc, rtitle = getContent(page) -- get file description and title after following any redirect if desc and desc ~= "" then -- found description on local wiki if mw.ustring.match(desc, "[Nn]on%-free") then return false end desc = mw.ustring.gsub(desc, "%b{}", striptemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess elseif not rtitle then return false else -- try commons desc = "{{" .. rtitle .. "}}" end frame = frame or mw.getCurrentFrame() desc = frame:preprocess(desc) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position); if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = mw.ustring.gsub(image, "|%s*frameless%s*%f[|%]]", "") -- make image a thumbnail, not frameless etc. image = mw.ustring.gsub(image, "|%s*framed?%s*%f[|%]]", "") if not matchany(image, "|%s*", {"thumb", "thumbnail"}, "%s*%f[|%]]") then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|thumb%1") end if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original if fileargs then image = mw.ustring.gsub(image, "(%]%]%s*)$", "|" .. fileargs .. "%1") end filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return filetext, text end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if it's the start of the article (blank lead) end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits if mw.ustring.find(text, "[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]") then -- avoid expensive search if possible text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text between onlyinclude sections text = mw.ustring.gsub(text, "^.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text before first onlyinclude section text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.*", "") -- remove text after last onlyinclude section end text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Remove unmatched <tag> or </tag> tags local function fixtags(text, tag) local startcount = 0 for i in mw.ustring.gmatch(text, "<%s*" .. tag .. "%f[^%w_].->") do startcount = startcount + 1 end local endcount = 0 for i in mw.ustring.gmatch(text, "<%s*/" .. tag .. "%f[^%w_].->") do endcount = endcount + 1 end if startcount > endcount then -- more <tag> than </tag>: remove the last few <tag>s local i = 0 text = mw.ustring.gsub(text, "<%s*" .. tag .. "%f[^%w_].->", function(t) i = i + 1 if i > endcount then return "" else return nil end end) -- "end" here terminates the anonymous replacement function(t) passed to gsub elseif endcount > startcount then -- more </tag> than <tag>: remove the first few </tag>s text = mw.ustring.gsub(text, "<%s*/" .. tag .. "%f[^%w_].->", "", endcount - startcount) end return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) local filetext filetext, text = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "()'''(.-'*)'''", function(a, b) if a < 100 and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return "'''[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]]''' else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end text = filetext .. text -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", ""); -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. -- Ensure div tags match text = fixtags(text, "div") if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p aumdljs7truwgykbv7zu12ozlmssruz 797310 797309 2019-09-06T12:52:14Z en>Certes 0 Improve fileargs handling: when adding |left to image specification, remove |right, etc. 797310 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Initialise this big table once to save time local unwantedTemplates = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", "[Ii]nflation[ _/-]*[Ff]n", "[Bb]iblesource", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note", -- Explain and others "[Ee]xplain", "[Ff]urther[ ]*explanation[ ]*needed", "[Ee]laboration[ ]*needed", "[Ee]xplanation[ ]*needed", -- TOC templates "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Tt][Oo][Cc][8]*[5]*", "[Tt][Oo][Cc]", "09[Aa][Zz]", "[Tt][Oo][Cc][ ]*[Cc][Oo][Mm][Pp][Aa][Cc][Tt]", "[Tt][Oo][Cc][ ]*[Ss][Mm][Aa][Ll][Ll]", "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Aa][Ll][Pp][Hh][Aa][Bb][Ee][Tt][Ii][Cc][ _]*[Tt][Oo][Cc]", "DEFAULTSORT:.-", "[Oo]ne[ _]+source" } -- Help gsub to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchany(t, "^{{%s*", unwantedTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noref = mw.ustring.gsub(noref, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end return title:getContent(), redir or title.prefixedText end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc, rtitle = getContent(page) -- get file description and title after following any redirect if desc and desc ~= "" then -- found description on local wiki if mw.ustring.match(desc, "[Nn]on%-free") then return false end desc = mw.ustring.gsub(desc, "%b{}", striptemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess elseif not rtitle then return false else -- try commons desc = "{{" .. rtitle .. "}}" end frame = frame or mw.getCurrentFrame() desc = frame:preprocess(desc) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position) if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end local imageArgGroups = { {"thumb", "thumbnail", "frame", "framed", "frameless"}, {"right", "left", "center", "none"}, {"baseline", "middle", "sub", "super", "text-top", "text-bottom", "top", "bottom"} } local function modifyImage(image, fileargs) if fileargs then for _, filearg in pairs(mw.text.split(fileargs, "|")) do -- handle fileargs=left|border etc. local fa = mw.ustring.gsub(filearg, "=.*", "") -- "upright=0.75" → "upright" local group = {fa}; -- group of "border" is ["border"]... for _, g in pairs(imageArgGroups) do for _, a in pairs(g) do if fa == a then group = g end -- ...but group of "left" is ["right", "left", "center", "none"] end end for _, a in pairs(group) do image = mw.ustring.gsub(image, "|%s*" .. a .. "%f[%A]%s*=[^|%]]*", ""); -- remove "|upright=0.75" etc. image = mw.ustring.gsub(image, "|%s*" .. a .. "%s*([|%]])", "%1"); -- replace "|left|" by "|" etc. end image = mw.ustring.gsub(image, "([|%]])", "|" .. filearg .. "%1", 1) -- replace "|" by "|left|" etc. end end return image end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = modifyImage(image, "thumb") image = modifyImage(image, fileargs) filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original image = modifyImage(image, fileargs); filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return filetext, text end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if it's the start of the article (blank lead) end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits if mw.ustring.find(text, "[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]") then -- avoid expensive search if possible text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text between onlyinclude sections text = mw.ustring.gsub(text, "^.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text before first onlyinclude section text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.*", "") -- remove text after last onlyinclude section end text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Remove unmatched <tag> or </tag> tags local function fixtags(text, tag) local startcount = 0 for i in mw.ustring.gmatch(text, "<%s*" .. tag .. "%f[^%w_].->") do startcount = startcount + 1 end local endcount = 0 for i in mw.ustring.gmatch(text, "<%s*/" .. tag .. "%f[^%w_].->") do endcount = endcount + 1 end if startcount > endcount then -- more <tag> than </tag>: remove the last few <tag>s local i = 0 text = mw.ustring.gsub(text, "<%s*" .. tag .. "%f[^%w_].->", function(t) i = i + 1 if i > endcount then return "" else return nil end end) -- "end" here terminates the anonymous replacement function(t) passed to gsub elseif endcount > startcount then -- more </tag> than <tag>: remove the first few </tag>s text = mw.ustring.gsub(text, "<%s*/" .. tag .. "%f[^%w_].->", "", endcount - startcount) end return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then pagename = mw.ustring.match(pagename, "%[%[%s*(.-)[%]|]") or pagename -- "[[Foo|Bar]]" → "Foo" pagename = mw.ustring.gsub(pagename, "^%s+", "") -- strip leading ... pagename = mw.ustring.gsub(pagename, "%s+$", "") -- ...and trailing white space if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) local filetext filetext, text = parse(text, options) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "()'''(.-'*)'''", function(a, b) if a < 100 and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return "'''[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]]''' else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end text = filetext .. text -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", ""); -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. -- Ensure div tags match text = fixtags(text, "div") if options.more then text = text .. " '''[[" .. pagename .. "|" .. options.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p qgdq8o9w3j1qsciyif4ybcagjaj7v7v 797311 797310 2019-09-12T11:26:46Z en>Certes 0 Add per-page options 797311 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Initialise this big table once to save time local unwantedTemplates = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", "[Ii]nflation[ _/-]*[Ff]n", "[Bb]iblesource", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note", -- Explain and others "[Ee]xplain", "[Ff]urther[ ]*explanation[ ]*needed", "[Ee]laboration[ ]*needed", "[Ee]xplanation[ ]*needed", -- TOC templates "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Tt][Oo][Cc][8]*[5]*", "[Tt][Oo][Cc]", "09[Aa][Zz]", "[Tt][Oo][Cc][ ]*[Cc][Oo][Mm][Pp][Aa][Cc][Tt]", "[Tt][Oo][Cc][ ]*[Ss][Mm][Aa][Ll][Ll]", "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Aa][Ll][Pp][Hh][Aa][Bb][Ee][Tt][Ii][Cc][ _]*[Tt][Oo][Cc]", "DEFAULTSORT:.-", "[Oo]ne[ _]+source" } -- Help gsub to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchany(t, "^{{%s*", unwantedTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noref = mw.ustring.gsub(noref, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end return title:getContent(), redir or title.prefixedText end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc, rtitle = getContent(page) -- get file description and title after following any redirect if desc and desc ~= "" then -- found description on local wiki if mw.ustring.match(desc, "[Nn]on%-free") then return false end desc = mw.ustring.gsub(desc, "%b{}", striptemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess elseif not rtitle then return false else -- try commons desc = "{{" .. rtitle .. "}}" end frame = frame or mw.getCurrentFrame() desc = frame:preprocess(desc) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position) if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end local imageArgGroups = { {"thumb", "thumbnail", "frame", "framed", "frameless"}, {"right", "left", "center", "none"}, {"baseline", "middle", "sub", "super", "text-top", "text-bottom", "top", "bottom"} } local function modifyImage(image, fileargs) if fileargs then for _, filearg in pairs(mw.text.split(fileargs, "|")) do -- handle fileargs=left|border etc. local fa = mw.ustring.gsub(filearg, "=.*", "") -- "upright=0.75" → "upright" local group = {fa}; -- group of "border" is ["border"]... for _, g in pairs(imageArgGroups) do for _, a in pairs(g) do if fa == a then group = g end -- ...but group of "left" is ["right", "left", "center", "none"] end end for _, a in pairs(group) do image = mw.ustring.gsub(image, "|%s*" .. a .. "%f[%A]%s*=[^|%]]*", ""); -- remove "|upright=0.75" etc. image = mw.ustring.gsub(image, "|%s*" .. a .. "%s*([|%]])", "%1"); -- replace "|left|" by "|" etc. end image = mw.ustring.gsub(image, "([|%]])", "|" .. filearg .. "%1", 1) -- replace "|" by "|left|" etc. end end return image end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = modifyImage(image, "thumb") image = modifyImage(image, fileargs) filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original image = modifyImage(image, fileargs); filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return filetext, text end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if it's the start of the article (blank lead) end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits if mw.ustring.find(text, "[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]") then -- avoid expensive search if possible text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text between onlyinclude sections text = mw.ustring.gsub(text, "^.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text before first onlyinclude section text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.*", "") -- remove text after last onlyinclude section end text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Remove unmatched <tag> or </tag> tags local function fixtags(text, tag) local startcount = 0 for i in mw.ustring.gmatch(text, "<%s*" .. tag .. "%f[^%w_].->") do startcount = startcount + 1 end local endcount = 0 for i in mw.ustring.gmatch(text, "<%s*/" .. tag .. "%f[^%w_].->") do endcount = endcount + 1 end if startcount > endcount then -- more <tag> than </tag>: remove the last few <tag>s local i = 0 text = mw.ustring.gsub(text, "<%s*" .. tag .. "%f[^%w_].->", function(t) i = i + 1 if i > endcount then return "" else return nil end end) -- "end" here terminates the anonymous replacement function(t) passed to gsub elseif endcount > startcount then -- more </tag> than <tag>: remove the first few </tag>s text = mw.ustring.gsub(text, "<%s*/" .. tag .. "%f[^%w_].->", "", endcount - startcount) end return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted local gotopt local pageoptstr -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then -- We have page or [[page]] or [[page|text]], possibly followed by |opt1|opt2... local pn pn, gotopt, pageoptstr = mw.ustring.match(pagename, "^%s*(%[%b[]%])%s*(|?)(.*)") if pn then pagename = mw.ustring.match(pn, "%[%[([^|%]]*)") -- turn [[page|text]] into page, discarding text else -- we have page or page|opt... pagename, gotopt, pageoptstr = mw.ustring.match(pagename, "%s*([^|]*[^|%s])%s*(|?)(.*)") end if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) local pageopts = {} -- pageopts (even if value is "") have priority over global options for k, v in pairs(options) do pageopts[k] = v end if gotopt and gotopt ~= "" then for _, t in pairs(mw.text.split(pageoptstr, "|")) do local k, v = mw.ustring.match(t, "%s*([^=]-)%s*=(.-)%s*$") pageopts[k] = v end pageopts.paraflags = numberflags(pageopts["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} pageopts.fileflags = numberflags(pageopts["files"] or "") -- parse file numbers if pageopts.more and pageopts.more == "" then pageopts.more = "Read more..." end -- more= is short for this default text end local filetext filetext, text = parse(text, pageopts) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "()'''(.-'*)'''", function(a, b) if a < 100 and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return "'''[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]]''' else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end text = filetext .. text -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", ""); -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. -- Ensure div tags match text = fixtags(text, "div") if pageopts.more then text = text .. " '''[[" .. pagename .. "|" .. pageopts.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p h7i4pcd6m6lw8pitvobstzz2kiw7mfh 797312 797311 2019-09-12T13:27:02Z en>Certes 0 Add named image files 797312 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Initialise this big table once to save time local unwantedTemplates = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", "[Ii]nflation[ _/-]*[Ff]n", "[Bb]iblesource", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note", -- Explain and others "[Ee]xplain", "[Ff]urther[ ]*explanation[ ]*needed", "[Ee]laboration[ ]*needed", "[Ee]xplanation[ ]*needed", -- TOC templates "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Tt][Oo][Cc][8]*[5]*", "[Tt][Oo][Cc]", "09[Aa][Zz]", "[Tt][Oo][Cc][ ]*[Cc][Oo][Mm][Pp][Aa][Cc][Tt]", "[Tt][Oo][Cc][ ]*[Ss][Mm][Aa][Ll][Ll]", "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Aa][Ll][Pp][Hh][Aa][Bb][Ee][Tt][Ii][Cc][ _]*[Tt][Oo][Cc]", "DEFAULTSORT:.-", "[Oo]ne[ _]+source" } -- Help gsub to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchany(t, "^{{%s*", unwantedTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noref = mw.ustring.gsub(noref, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end return title:getContent(), redir or title.prefixedText end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc, rtitle = getContent(page) -- get file description and title after following any redirect if desc and desc ~= "" then -- found description on local wiki if mw.ustring.match(desc, "[Nn]on%-free") then return false end desc = mw.ustring.gsub(desc, "%b{}", striptemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess elseif not rtitle then return false else -- try commons desc = "{{" .. rtitle .. "}}" end frame = frame or mw.getCurrentFrame() desc = frame:preprocess(desc) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position) if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end local imageArgGroups = { {"thumb", "thumbnail", "frame", "framed", "frameless"}, {"right", "left", "center", "none"}, {"baseline", "middle", "sub", "super", "text-top", "text-bottom", "top", "bottom"} } local function modifyImage(image, fileargs) if fileargs then for _, filearg in pairs(mw.text.split(fileargs, "|")) do -- handle fileargs=left|border etc. local fa = mw.ustring.gsub(filearg, "=.*", "") -- "upright=0.75" → "upright" local group = {fa}; -- group of "border" is ["border"]... for _, g in pairs(imageArgGroups) do for _, a in pairs(g) do if fa == a then group = g end -- ...but group of "left" is ["right", "left", "center", "none"] end end for _, a in pairs(group) do image = mw.ustring.gsub(image, "|%s*" .. a .. "%f[%A]%s*=[^|%]]*", ""); -- remove "|upright=0.75" etc. image = mw.ustring.gsub(image, "|%s*" .. a .. "%s*([|%]])", "%1"); -- replace "|left|" by "|" etc. end image = mw.ustring.gsub(image, "([|%]])", "|" .. filearg .. "%1", 1) -- replace "|" by "|left|" etc. end end return image end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space -- Add named files local f = options.files if f and mw.ustring.match(f, "[^%d%s%-,]") then -- filename rather than number list f = mw.ustring.gsub(f, "^%s*File%s*:%s*", "", 1) f = mw.ustring.gsub(f, "^%s*Image%s*:%s*", "", 1) f = "[[File:" .. f .. "]]" f = modifyImage(f, "thumb") f = modifyImage(f, fileargs) if checkimage(f) then filetext = filetext .. f .. "\n" end end repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = modifyImage(image, "thumb") image = modifyImage(image, fileargs) filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original image = modifyImage(image, fileargs); filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return filetext, text end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if it's the start of the article (blank lead) end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits if mw.ustring.find(text, "[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]") then -- avoid expensive search if possible text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text between onlyinclude sections text = mw.ustring.gsub(text, "^.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text before first onlyinclude section text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.*", "") -- remove text after last onlyinclude section end text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Remove unmatched <tag> or </tag> tags local function fixtags(text, tag) local startcount = 0 for i in mw.ustring.gmatch(text, "<%s*" .. tag .. "%f[^%w_].->") do startcount = startcount + 1 end local endcount = 0 for i in mw.ustring.gmatch(text, "<%s*/" .. tag .. "%f[^%w_].->") do endcount = endcount + 1 end if startcount > endcount then -- more <tag> than </tag>: remove the last few <tag>s local i = 0 text = mw.ustring.gsub(text, "<%s*" .. tag .. "%f[^%w_].->", function(t) i = i + 1 if i > endcount then return "" else return nil end end) -- "end" here terminates the anonymous replacement function(t) passed to gsub elseif endcount > startcount then -- more </tag> than <tag>: remove the first few </tag>s text = mw.ustring.gsub(text, "<%s*/" .. tag .. "%f[^%w_].->", "", endcount - startcount) end return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted local gotopt local pageoptstr -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then -- We have page or [[page]] or [[page|text]], possibly followed by |opt1|opt2... local pn pn, gotopt, pageoptstr = mw.ustring.match(pagename, "^%s*(%[%b[]%])%s*(|?)(.*)") if pn then pagename = mw.ustring.match(pn, "%[%[([^|%]]*)") -- turn [[page|text]] into page, discarding text else -- we have page or page|opt... pagename, gotopt, pageoptstr = mw.ustring.match(pagename, "%s*([^|]*[^|%s])%s*(|?)(.*)") end if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) local pageopts = {} -- pageopts (even if value is "") have priority over global options for k, v in pairs(options) do pageopts[k] = v end if gotopt and gotopt ~= "" then for _, t in pairs(mw.text.split(pageoptstr, "|")) do local k, v = mw.ustring.match(t, "%s*([^=]-)%s*=(.-)%s*$") pageopts[k] = v end pageopts.paraflags = numberflags(pageopts["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} pageopts.fileflags = numberflags(pageopts["files"] or "") -- parse file numbers if pageopts.more and pageopts.more == "" then pageopts.more = "Read more..." end -- more= is short for this default text end local filetext filetext, text = parse(text, pageopts) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "()'''(.-'*)'''", function(a, b) if a < 100 and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return "'''[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]]''' else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end text = filetext .. text -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", ""); -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. -- Ensure div tags match text = fixtags(text, "div") if pageopts.more then text = text .. " '''[[" .. pagename .. "|" .. pageopts.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = main(pagenames, options) return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p osilkptk98001wikt31cbgdtb3f7n93 797313 797312 2019-10-20T15:26:08Z en>Certes 0 Add showall= to show all excerpts from selectable articles 797313 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Initialise this big table once to save time local unwantedTemplates = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", "[Ii]nflation[ _/-]*[Ff]n", "[Bb]iblesource", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note", -- Explain and others "[Ee]xplain", "[Ff]urther[ ]*explanation[ ]*needed", "[Ee]laboration[ ]*needed", "[Ee]xplanation[ ]*needed", -- TOC templates "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Tt][Oo][Cc][8]*[5]*", "[Tt][Oo][Cc]", "09[Aa][Zz]", "[Tt][Oo][Cc][ ]*[Cc][Oo][Mm][Pp][Aa][Cc][Tt]", "[Tt][Oo][Cc][ ]*[Ss][Mm][Aa][Ll][Ll]", "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Aa][Ll][Pp][Hh][Aa][Bb][Ee][Tt][Ii][Cc][ _]*[Tt][Oo][Cc]", "DEFAULTSORT:.-", "[Oo]ne[ _]+source" } -- Help gsub to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchany(t, "^{{%s*", unwantedTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noref = mw.ustring.gsub(noref, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end return title:getContent(), redir or title.prefixedText end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc, rtitle = getContent(page) -- get file description and title after following any redirect if desc and desc ~= "" then -- found description on local wiki if mw.ustring.match(desc, "[Nn]on%-free") then return false end desc = mw.ustring.gsub(desc, "%b{}", striptemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess elseif not rtitle then return false else -- try commons desc = "{{" .. rtitle .. "}}" end frame = frame or mw.getCurrentFrame() desc = frame:preprocess(desc) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position) if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end local imageArgGroups = { {"thumb", "thumbnail", "frame", "framed", "frameless"}, {"right", "left", "center", "none"}, {"baseline", "middle", "sub", "super", "text-top", "text-bottom", "top", "bottom"} } local function modifyImage(image, fileargs) if fileargs then for _, filearg in pairs(mw.text.split(fileargs, "|")) do -- handle fileargs=left|border etc. local fa = mw.ustring.gsub(filearg, "=.*", "") -- "upright=0.75" → "upright" local group = {fa}; -- group of "border" is ["border"]... for _, g in pairs(imageArgGroups) do for _, a in pairs(g) do if fa == a then group = g end -- ...but group of "left" is ["right", "left", "center", "none"] end end for _, a in pairs(group) do image = mw.ustring.gsub(image, "|%s*" .. a .. "%f[%A]%s*=[^|%]]*", ""); -- remove "|upright=0.75" etc. image = mw.ustring.gsub(image, "|%s*" .. a .. "%s*([|%]])", "%1"); -- replace "|left|" by "|" etc. end image = mw.ustring.gsub(image, "([|%]])", "|" .. filearg .. "%1", 1) -- replace "|" by "|left|" etc. end end return image end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space -- Add named files local f = options.files if f and mw.ustring.match(f, "[^%d%s%-,]") then -- filename rather than number list f = mw.ustring.gsub(f, "^%s*File%s*:%s*", "", 1) f = mw.ustring.gsub(f, "^%s*Image%s*:%s*", "", 1) f = "[[File:" .. f .. "]]" f = modifyImage(f, "thumb") f = modifyImage(f, fileargs) if checkimage(f) then filetext = filetext .. f .. "\n" end end repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = modifyImage(image, "thumb") image = modifyImage(image, fileargs) filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original image = modifyImage(image, fileargs); filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return filetext, text end local function cleanupText(text, leadOnly) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if it's the start of the article (blank lead) end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits if mw.ustring.find(text, "[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]") then -- avoid expensive search if possible text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text between onlyinclude sections text = mw.ustring.gsub(text, "^.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text before first onlyinclude section text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.*", "") -- remove text after last onlyinclude section end text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Remove unmatched <tag> or </tag> tags local function fixtags(text, tag) local startcount = 0 for i in mw.ustring.gmatch(text, "<%s*" .. tag .. "%f[^%w_].->") do startcount = startcount + 1 end local endcount = 0 for i in mw.ustring.gmatch(text, "<%s*/" .. tag .. "%f[^%w_].->") do endcount = endcount + 1 end if startcount > endcount then -- more <tag> than </tag>: remove the last few <tag>s local i = 0 text = mw.ustring.gsub(text, "<%s*" .. tag .. "%f[^%w_].->", function(t) i = i + 1 if i > endcount then return "" else return nil end end) -- "end" here terminates the anonymous replacement function(t) passed to gsub elseif endcount > startcount then -- more </tag> than <tag>: remove the first few </tag>s text = mw.ustring.gsub(text, "<%s*/" .. tag .. "%f[^%w_].->", "", endcount - startcount) end return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted local gotopt local pageoptstr -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then -- We have page or [[page]] or [[page|text]], possibly followed by |opt1|opt2... local pn pn, gotopt, pageoptstr = mw.ustring.match(pagename, "^%s*(%[%b[]%])%s*(|?)(.*)") if pn then pagename = mw.ustring.match(pn, "%[%[([^|%]]*)") -- turn [[page|text]] into page, discarding text else -- we have page or page|opt... pagename, gotopt, pageoptstr = mw.ustring.match(pagename, "%s*([^|]*[^|%s])%s*(|?)(.*)") end if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end text = cleanupText(text, true) local pageopts = {} -- pageopts (even if value is "") have priority over global options for k, v in pairs(options) do pageopts[k] = v end if gotopt and gotopt ~= "" then for _, t in pairs(mw.text.split(pageoptstr, "|")) do local k, v = mw.ustring.match(t, "%s*([^=]-)%s*=(.-)%s*$") pageopts[k] = v end pageopts.paraflags = numberflags(pageopts["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} pageopts.fileflags = numberflags(pageopts["files"] or "") -- parse file numbers if pageopts.more and pageopts.more == "" then pageopts.more = "Read more..." end -- more= is short for this default text end local filetext filetext, text = parse(text, pageopts) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "()'''(.-'*)'''", function(a, b) if a < 100 and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return "'''[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]]''' else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end text = filetext .. text -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", ""); -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. -- Ensure div tags match text = fixtags(text, "div") if pageopts.more then text = text .. " '''[[" .. pagename .. "|" .. pageopts.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = "" if options.showall then local separator = "" for _, p in pairs(pagenames) do local t = main({ p }, options) if t ~= "" then text = text .. separator .. t separator = options.showall if separator == "" then separator = "{{clear}}{{hr}}" end end end else text = main(pagenames, options) end return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly) return cleanupText(text, leadOnly) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p f6jxfh5ddyytr3llz8w4d11q4076yfh 797314 797313 2020-01-07T18:02:06Z en>Sophivorus 0 Add option to keep references, see talk page 797314 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Initialise this big table once to save time local unwantedTemplates = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", "[Ii]nflation[ _/-]*[Ff]n", "[Bb]iblesource", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note", -- Explain and others "[Ee]xplain", "[Ff]urther[ ]*explanation[ ]*needed", "[Ee]laboration[ ]*needed", "[Ee]xplanation[ ]*needed", -- TOC templates "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Tt][Oo][Cc][8]*[5]*", "[Tt][Oo][Cc]", "09[Aa][Zz]", "[Tt][Oo][Cc][ ]*[Cc][Oo][Mm][Pp][Aa][Cc][Tt]", "[Tt][Oo][Cc][ ]*[Ss][Mm][Aa][Ll][Ll]", "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Aa][Ll][Pp][Hh][Aa][Bb][Ee][Tt][Ii][Cc][ _]*[Tt][Oo][Cc]", "DEFAULTSORT:.-", "[Oo]ne[ _]+source" } -- Help gsub to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchany(t, "^{{%s*", unwantedTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noref = mw.ustring.gsub(noref, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end return title:getContent(), redir or title.prefixedText end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc, rtitle = getContent(page) -- get file description and title after following any redirect if desc and desc ~= "" then -- found description on local wiki if mw.ustring.match(desc, "[Nn]on%-free") then return false end desc = mw.ustring.gsub(desc, "%b{}", striptemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess elseif not rtitle then return false else -- try commons desc = "{{" .. rtitle .. "}}" end frame = frame or mw.getCurrentFrame() desc = frame:preprocess(desc) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position) if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end local imageArgGroups = { {"thumb", "thumbnail", "frame", "framed", "frameless"}, {"right", "left", "center", "none"}, {"baseline", "middle", "sub", "super", "text-top", "text-bottom", "top", "bottom"} } local function modifyImage(image, fileargs) if fileargs then for _, filearg in pairs(mw.text.split(fileargs, "|")) do -- handle fileargs=left|border etc. local fa = mw.ustring.gsub(filearg, "=.*", "") -- "upright=0.75" → "upright" local group = {fa}; -- group of "border" is ["border"]... for _, g in pairs(imageArgGroups) do for _, a in pairs(g) do if fa == a then group = g end -- ...but group of "left" is ["right", "left", "center", "none"] end end for _, a in pairs(group) do image = mw.ustring.gsub(image, "|%s*" .. a .. "%f[%A]%s*=[^|%]]*", ""); -- remove "|upright=0.75" etc. image = mw.ustring.gsub(image, "|%s*" .. a .. "%s*([|%]])", "%1"); -- replace "|left|" by "|" etc. end image = mw.ustring.gsub(image, "([|%]])", "|" .. filearg .. "%1", 1) -- replace "|" by "|left|" etc. end end return image end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space -- Add named files local f = options.files if f and mw.ustring.match(f, "[^%d%s%-,]") then -- filename rather than number list f = mw.ustring.gsub(f, "^%s*File%s*:%s*", "", 1) f = mw.ustring.gsub(f, "^%s*Image%s*:%s*", "", 1) f = "[[File:" .. f .. "]]" f = modifyImage(f, "thumb") f = modifyImage(f, fileargs) if checkimage(f) then filetext = filetext .. f .. "\n" end end repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = modifyImage(image, "thumb") image = modifyImage(image, fileargs) filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original image = modifyImage(image, fileargs); filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return filetext, text end local function cleanupText(text, leadOnly, keepRefs) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if it's the start of the article (blank lead) end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits if mw.ustring.find(text, "[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]") then -- avoid expensive search if possible text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text between onlyinclude sections text = mw.ustring.gsub(text, "^.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text before first onlyinclude section text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.*", "") -- remove text after last onlyinclude section end if not keepRefs then text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs end text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Remove unmatched <tag> or </tag> tags local function fixtags(text, tag) local startcount = 0 for i in mw.ustring.gmatch(text, "<%s*" .. tag .. "%f[^%w_].->") do startcount = startcount + 1 end local endcount = 0 for i in mw.ustring.gmatch(text, "<%s*/" .. tag .. "%f[^%w_].->") do endcount = endcount + 1 end if startcount > endcount then -- more <tag> than </tag>: remove the last few <tag>s local i = 0 text = mw.ustring.gsub(text, "<%s*" .. tag .. "%f[^%w_].->", function(t) i = i + 1 if i > endcount then return "" else return nil end end) -- "end" here terminates the anonymous replacement function(t) passed to gsub elseif endcount > startcount then -- more </tag> than <tag>: remove the first few </tag>s text = mw.ustring.gsub(text, "<%s*/" .. tag .. "%f[^%w_].->", "", endcount - startcount) end return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted local gotopt local pageoptstr -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then -- We have page or [[page]] or [[page|text]], possibly followed by |opt1|opt2... local pn pn, gotopt, pageoptstr = mw.ustring.match(pagename, "^%s*(%[%b[]%])%s*(|?)(.*)") if pn then pagename = mw.ustring.match(pn, "%[%[([^|%]]*)") -- turn [[page|text]] into page, discarding text else -- we have page or page|opt... pagename, gotopt, pageoptstr = mw.ustring.match(pagename, "%s*([^|]*[^|%s])%s*(|?)(.*)") end if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end local keepRefs = options.keepRefs text = cleanupText(text, true, keepRefs) local pageopts = {} -- pageopts (even if value is "") have priority over global options for k, v in pairs(options) do pageopts[k] = v end if gotopt and gotopt ~= "" then for _, t in pairs(mw.text.split(pageoptstr, "|")) do local k, v = mw.ustring.match(t, "%s*([^=]-)%s*=(.-)%s*$") pageopts[k] = v end pageopts.paraflags = numberflags(pageopts["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} pageopts.fileflags = numberflags(pageopts["files"] or "") -- parse file numbers if pageopts.more and pageopts.more == "" then pageopts.more = "Read more..." end -- more= is short for this default text end local filetext filetext, text = parse(text, pageopts) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "()'''(.-'*)'''", function(a, b) if a < 100 and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return "'''[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]]''' else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end text = filetext .. text -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", ""); -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. -- Ensure div tags match text = fixtags(text, "div") if pageopts.more then text = text .. " '''[[" .. pagename .. "|" .. pageopts.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = "" if options.showall then local separator = "" for _, p in pairs(pagenames) do local t = main({ p }, options) if t ~= "" then text = text .. separator .. t separator = options.showall if separator == "" then separator = "{{clear}}{{hr}}" end end end else text = main(pagenames, options) end return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly, keepRefs) return cleanupText(text, leadOnly, keepRefs) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p iyukx0t2wp799whq43itr6s1j0583bw 797315 797314 2020-01-07T18:06:34Z en>Sophivorus 0 797315 Scribunto text/plain local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Initialise this big table once to save time local unwantedTemplates = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", "[Ii]nflation[ _/-]*[Ff]n", "[Bb]iblesource", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note", -- Explain and others "[Ee]xplain", "[Ff]urther[ ]*explanation[ ]*needed", "[Ee]laboration[ ]*needed", "[Ee]xplanation[ ]*needed", -- TOC templates "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Tt][Oo][Cc][8]*[5]*", "[Tt][Oo][Cc]", "09[Aa][Zz]", "[Tt][Oo][Cc][ ]*[Cc][Oo][Mm][Pp][Aa][Cc][Tt]", "[Tt][Oo][Cc][ ]*[Ss][Mm][Aa][Ll][Ll]", "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Aa][Ll][Pp][Hh][Aa][Bb][Ee][Tt][Ii][Cc][ _]*[Tt][Oo][Cc]", "DEFAULTSORT:.-", "[Oo]ne[ _]+source" } -- Help gsub to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchany(t, "^{{%s*", unwantedTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noref = mw.ustring.gsub(noref, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end return title:getContent(), redir or title.prefixedText end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", {"[Ff]ile", "[Ii]mage"}, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc, rtitle = getContent(page) -- get file description and title after following any redirect if desc and desc ~= "" then -- found description on local wiki if mw.ustring.match(desc, "[Nn]on%-free") then return false end desc = mw.ustring.gsub(desc, "%b{}", striptemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess elseif not rtitle then return false else -- try commons desc = "{{" .. rtitle .. "}}" end frame = frame or mw.getCurrentFrame() desc = frame:preprocess(desc) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", {"[Ff]ile", "[Ii]mage"}, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position) if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", {"[Ff]ile", "[Ii]mage"}, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", {"[Ii]mage:", "[Ff]ile:"}, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end local imageArgGroups = { {"thumb", "thumbnail", "frame", "framed", "frameless"}, {"right", "left", "center", "none"}, {"baseline", "middle", "sub", "super", "text-top", "text-bottom", "top", "bottom"} } local function modifyImage(image, fileargs) if fileargs then for _, filearg in pairs(mw.text.split(fileargs, "|")) do -- handle fileargs=left|border etc. local fa = mw.ustring.gsub(filearg, "=.*", "") -- "upright=0.75" → "upright" local group = {fa}; -- group of "border" is ["border"]... for _, g in pairs(imageArgGroups) do for _, a in pairs(g) do if fa == a then group = g end -- ...but group of "left" is ["right", "left", "center", "none"] end end for _, a in pairs(group) do image = mw.ustring.gsub(image, "|%s*" .. a .. "%f[%A]%s*=[^|%]]*", ""); -- remove "|upright=0.75" etc. image = mw.ustring.gsub(image, "|%s*" .. a .. "%s*([|%]])", "%1"); -- replace "|left|" by "|" etc. end image = mw.ustring.gsub(image, "([|%]])", "|" .. filearg .. "%1", 1) -- replace "|" by "|left|" etc. end end return image end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space -- Add named files local f = options.files if f and mw.ustring.match(f, "[^%d%s%-,]") then -- filename rather than number list f = mw.ustring.gsub(f, "^%s*File%s*:%s*", "", 1) f = mw.ustring.gsub(f, "^%s*Image%s*:%s*", "", 1) f = "[[File:" .. f .. "]]" f = modifyImage(f, "thumb") f = modifyImage(f, fileargs) if checkimage(f) then filetext = filetext .. f .. "\n" end end repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = modifyImage(image, "thumb") image = modifyImage(image, fileargs) filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original image = modifyImage(image, fileargs); filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return filetext, text end local function cleanupText(text, leadOnly, keepRefs) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if it's the start of the article (blank lead) end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits if mw.ustring.find(text, "[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]") then -- avoid expensive search if possible text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text between onlyinclude sections text = mw.ustring.gsub(text, "^.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text before first onlyinclude section text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.*", "") -- remove text after last onlyinclude section end if not keepRefs then text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references end text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Remove unmatched <tag> or </tag> tags local function fixtags(text, tag) local startcount = 0 for i in mw.ustring.gmatch(text, "<%s*" .. tag .. "%f[^%w_].->") do startcount = startcount + 1 end local endcount = 0 for i in mw.ustring.gmatch(text, "<%s*/" .. tag .. "%f[^%w_].->") do endcount = endcount + 1 end if startcount > endcount then -- more <tag> than </tag>: remove the last few <tag>s local i = 0 text = mw.ustring.gsub(text, "<%s*" .. tag .. "%f[^%w_].->", function(t) i = i + 1 if i > endcount then return "" else return nil end end) -- "end" here terminates the anonymous replacement function(t) passed to gsub elseif endcount > startcount then -- more </tag> than <tag>: remove the first few </tag>s text = mw.ustring.gsub(text, "<%s*/" .. tag .. "%f[^%w_].->", "", endcount - startcount) end return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted local gotopt local pageoptstr -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then -- We have page or [[page]] or [[page|text]], possibly followed by |opt1|opt2... local pn pn, gotopt, pageoptstr = mw.ustring.match(pagename, "^%s*(%[%b[]%])%s*(|?)(.*)") if pn then pagename = mw.ustring.match(pn, "%[%[([^|%]]*)") -- turn [[page|text]] into page, discarding text else -- we have page or page|opt... pagename, gotopt, pageoptstr = mw.ustring.match(pagename, "%s*([^|]*[^|%s])%s*(|?)(.*)") end if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end local keepRefs = options.keepRefs text = cleanupText(text, true, keepRefs) local pageopts = {} -- pageopts (even if value is "") have priority over global options for k, v in pairs(options) do pageopts[k] = v end if gotopt and gotopt ~= "" then for _, t in pairs(mw.text.split(pageoptstr, "|")) do local k, v = mw.ustring.match(t, "%s*([^=]-)%s*=(.-)%s*$") pageopts[k] = v end pageopts.paraflags = numberflags(pageopts["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} pageopts.fileflags = numberflags(pageopts["files"] or "") -- parse file numbers if pageopts.more and pageopts.more == "" then pageopts.more = "Read more..." end -- more= is short for this default text end local filetext filetext, text = parse(text, pageopts) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "()'''(.-'*)'''", function(a, b) if a < 100 and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return "'''[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]]''' else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end text = filetext .. text -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", ""); -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. -- Ensure div tags match text = fixtags(text, "div") if pageopts.more then text = text .. " '''[[" .. pagename .. "|" .. pageopts.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = "" if options.showall then local separator = "" for _, p in pairs(pagenames) do local t = main({ p }, options) if t ~= "" then text = text .. separator .. t separator = options.showall if separator == "" then separator = "{{clear}}{{hr}}" end end end else text = main(pagenames, options) end return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly, keepRefs) return cleanupText(text, leadOnly, keepRefs) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p oimz58jmmrszy8bm7n3c5n5fbxe44t4 797316 797315 2020-01-31T12:14:31Z en>Sophivorus 0 Update to latest sandbox version 797316 Scribunto text/plain -- Local aliases of the file namespace local fileNamespaces = { "[Ff]ile", "[Ii]mage" } -- The module keeps all inline templates except these ones local unwantedInlineTemplates = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", "[Ii]nflation[ _/-]*[Ff]n", "[Bb]iblesource", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note", -- Explain and others "[Ee]xplain", "[Ff]urther[ ]*explanation[ ]*needed", "[Ee]laboration[ ]*needed", "[Ee]xplanation[ ]*needed", -- TOC templates "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Tt][Oo][Cc][8]*[5]*", "[Tt][Oo][Cc]", "09[Aa][Zz]", "[Tt][Oo][Cc][ ]*[Cc][Oo][Mm][Pp][Aa][Cc][Tt]", "[Tt][Oo][Cc][ ]*[Ss][Mm][Aa][Ll][Ll]", "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Aa][Ll][Pp][Hh][Aa][Bb][Ee][Tt][Ii][Cc][ _]*[Tt][Oo][Cc]", "DEFAULTSORT:.-", "[Oo]ne[ _]+source" } -- The module removes all block templates except these ones local wantedBlockTemplates = { "[Hh]istorical populations" } local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Help gsub to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchany(t, "^{{%s*", unwantedInlineTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noref = mw.ustring.gsub(noref, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end return title:getContent(), redir or title.prefixedText end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", fileNamespaces, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc, rtitle = getContent(page) -- get file description and title after following any redirect if desc and desc ~= "" then -- found description on local wiki if mw.ustring.match(desc, "[Nn]on%-free") then return false end desc = mw.ustring.gsub(desc, "%b{}", striptemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess elseif not rtitle then return false else -- try commons desc = "{{" .. rtitle .. "}}" end frame = frame or mw.getCurrentFrame() desc = frame:preprocess(desc) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", fileNamespaces, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position) if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", fileNamespaces, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", fileNamespaces, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end local imageArgGroups = { {"thumb", "thumbnail", "frame", "framed", "frameless"}, {"right", "left", "center", "none"}, {"baseline", "middle", "sub", "super", "text-top", "text-bottom", "top", "bottom"} } local function modifyImage(image, fileargs) if fileargs then for _, filearg in pairs(mw.text.split(fileargs, "|")) do -- handle fileargs=left|border etc. local fa = mw.ustring.gsub(filearg, "=.*", "") -- "upright=0.75" → "upright" local group = {fa}; -- group of "border" is ["border"]... for _, g in pairs(imageArgGroups) do for _, a in pairs(g) do if fa == a then group = g end -- ...but group of "left" is ["right", "left", "center", "none"] end end for _, a in pairs(group) do image = mw.ustring.gsub(image, "|%s*" .. a .. "%f[%A]%s*=[^|%]]*", ""); -- remove "|upright=0.75" etc. image = mw.ustring.gsub(image, "|%s*" .. a .. "%s*([|%]])", "%1"); -- replace "|left|" by "|" etc. end image = mw.ustring.gsub(image, "([|%]])", "|" .. filearg .. "%1", 1) -- replace "|" by "|left|" etc. end end return image end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space -- Add named files local f = options.files if f and mw.ustring.match(f, "[^%d%s%-,]") then -- filename rather than number list f = mw.ustring.gsub(f, "^%s*File%s*:%s*", "", 1) f = mw.ustring.gsub(f, "^%s*Image%s*:%s*", "", 1) f = "[[File:" .. f .. "]]" f = modifyImage(f, "thumb") f = modifyImage(f, fileargs) if checkimage(f) then filetext = filetext .. f .. "\n" end end repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} or {| Table |} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif matchany(token, "{{%s*", wantedBlockTemplates, "%s*%f[|}]") then t = t .. token -- keep wanted block templates elseif options.keepTables and mw.ustring.sub(token, 1, 2) == '{|' then t = t .. token -- keep tables elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = modifyImage(image, "thumb") image = modifyImage(image, fileargs) filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original image = modifyImage(image, fileargs); filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$"); -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return filetext, text end local function cleanupText(text, leadOnly, keepRefs) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if it's the start of the article (blank lead) end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits if mw.ustring.find(text, "[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]") then -- avoid expensive search if possible text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text between onlyinclude sections text = mw.ustring.gsub(text, "^.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text before first onlyinclude section text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.*", "") -- remove text after last onlyinclude section end if not keepRefs then text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references end text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Remove unmatched <tag> or </tag> tags local function fixtags(text, tag) local startcount = 0 for i in mw.ustring.gmatch(text, "<%s*" .. tag .. "%f[^%w_].->") do startcount = startcount + 1 end local endcount = 0 for i in mw.ustring.gmatch(text, "<%s*/" .. tag .. "%f[^%w_].->") do endcount = endcount + 1 end if startcount > endcount then -- more <tag> than </tag>: remove the last few <tag>s local i = 0 text = mw.ustring.gsub(text, "<%s*" .. tag .. "%f[^%w_].->", function(t) i = i + 1 if i > endcount then return "" else return nil end end) -- "end" here terminates the anonymous replacement function(t) passed to gsub elseif endcount > startcount then -- more </tag> than <tag>: remove the first few </tag>s text = mw.ustring.gsub(text, "<%s*/" .. tag .. "%f[^%w_].->", "", endcount - startcount) end return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted local gotopt local pageoptstr -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then -- We have page or [[page]] or [[page|text]], possibly followed by |opt1|opt2... local pn pn, gotopt, pageoptstr = mw.ustring.match(pagename, "^%s*(%[%b[]%])%s*(|?)(.*)") if pn then pagename = mw.ustring.match(pn, "%[%[([^|%]]*)") -- turn [[page|text]] into page, discarding text else -- we have page or page|opt... pagename, gotopt, pageoptstr = mw.ustring.match(pagename, "%s*([^|]*[^|%s])%s*(|?)(.*)") end if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end local keepRefs if options.keepRefs == '1' then keepRefs = true else keepRefs = false end text = cleanupText(text, true, keepRefs) local pageopts = {} -- pageopts (even if value is "") have priority over global options for k, v in pairs(options) do pageopts[k] = v end if gotopt and gotopt ~= "" then for _, t in pairs(mw.text.split(pageoptstr, "|")) do local k, v = mw.ustring.match(t, "%s*([^=]-)%s*=(.-)%s*$") pageopts[k] = v end pageopts.paraflags = numberflags(pageopts["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} pageopts.fileflags = numberflags(pageopts["files"] or "") -- parse file numbers if pageopts.more and pageopts.more == "" then pageopts.more = "Read more..." end -- more= is short for this default text end local filetext filetext, text = parse(text, pageopts) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "()'''(.-'*)'''", function(a, b) if a < 100 and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return "'''[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]]''' else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end text = filetext .. text -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27"); -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2"); -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27"); until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", ""); -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", ""); -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", ""); -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. -- Ensure div tags match text = fixtags(text, "div") if pageopts.more then text = text .. " '''[[" .. pagename .. "|" .. pageopts.more .. "]]'''" end -- wikilink to article for more info return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = "" if options.showall then local separator = "" for _, p in pairs(pagenames) do local t = main({ p }, options) if t ~= "" then text = text .. separator .. t separator = options.showall if separator == "" then separator = "{{clear}}{{hr}}" end end end else text = main(pagenames, options) end return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly, keepRefs) return cleanupText(text, leadOnly, keepRefs) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p a8ck57zn3wnxyneelok81wo04abu731 797317 797316 2020-02-01T17:13:03Z en>Certes 0 list= option to add a collapsed list of pages which might appear; remove unnecessary semicolons 797317 Scribunto text/plain -- Local aliases of the file namespace local fileNamespaces = { "[Ff]ile", "[Ii]mage" } -- The module keeps all inline templates except these ones local unwantedInlineTemplates = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "NoteTag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", "[Ii]nflation[ _/-]*[Ff]n", "[Bb]iblesource", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note", -- Explain and others "[Ee]xplain", "[Ff]urther[ ]*explanation[ ]*needed", "[Ee]laboration[ ]*needed", "[Ee]xplanation[ ]*needed", -- TOC templates "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Tt][Oo][Cc][8]*[5]*", "[Tt][Oo][Cc]", "09[Aa][Zz]", "[Tt][Oo][Cc][ ]*[Cc][Oo][Mm][Pp][Aa][Cc][Tt]", "[Tt][Oo][Cc][ ]*[Ss][Mm][Aa][Ll][Ll]", "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Aa][Ll][Pp][Hh][Aa][Bb][Ee][Tt][Ii][Cc][ _]*[Tt][Oo][Cc]", "DEFAULTSORT:.-", "[Oo]ne[ _]+source" } -- The module removes all block templates except these ones local wantedBlockTemplates = { "[Hh]istorical populations" } local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Help gsub to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchany(t, "^{{%s*", unwantedInlineTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noref = mw.ustring.gsub(noref, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end return title:getContent(), redir or title.prefixedText end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", fileNamespaces, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc, rtitle = getContent(page) -- get file description and title after following any redirect if desc and desc ~= "" then -- found description on local wiki if mw.ustring.match(desc, "[Nn]on%-free") then return false end desc = mw.ustring.gsub(desc, "%b{}", striptemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess elseif not rtitle then return false else -- try commons desc = "{{" .. rtitle .. "}}" end frame = frame or mw.getCurrentFrame() desc = frame:preprocess(desc) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", fileNamespaces, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position) if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", fileNamespaces, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", fileNamespaces, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end local imageArgGroups = { {"thumb", "thumbnail", "frame", "framed", "frameless"}, {"right", "left", "center", "none"}, {"baseline", "middle", "sub", "super", "text-top", "text-bottom", "top", "bottom"} } local function modifyImage(image, fileargs) if fileargs then for _, filearg in pairs(mw.text.split(fileargs, "|")) do -- handle fileargs=left|border etc. local fa = mw.ustring.gsub(filearg, "=.*", "") -- "upright=0.75" → "upright" local group = {fa} -- group of "border" is ["border"]... for _, g in pairs(imageArgGroups) do for _, a in pairs(g) do if fa == a then group = g end -- ...but group of "left" is ["right", "left", "center", "none"] end end for _, a in pairs(group) do image = mw.ustring.gsub(image, "|%s*" .. a .. "%f[%A]%s*=[^|%]]*", "") -- remove "|upright=0.75" etc. image = mw.ustring.gsub(image, "|%s*" .. a .. "%s*([|%]])", "%1") -- replace "|left|" by "|" etc. end image = mw.ustring.gsub(image, "([|%]])", "|" .. filearg .. "%1", 1) -- replace "|" by "|left|" etc. end end return image end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space -- Add named files local f = options.files if f and mw.ustring.match(f, "[^%d%s%-,]") then -- filename rather than number list f = mw.ustring.gsub(f, "^%s*File%s*:%s*", "", 1) f = mw.ustring.gsub(f, "^%s*Image%s*:%s*", "", 1) f = "[[File:" .. f .. "]]" f = modifyImage(f, "thumb") f = modifyImage(f, fileargs) if checkimage(f) then filetext = filetext .. f .. "\n" end end repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} or {| Table |} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif matchany(token, "{{%s*", wantedBlockTemplates, "%s*%f[|}]") then t = t .. token -- keep wanted block templates elseif options.keepTables and mw.ustring.sub(token, 1, 2) == '{|' then t = t .. token -- keep tables elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = modifyImage(image, "thumb") image = modifyImage(image, fileargs) filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original image = modifyImage(image, fileargs) filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$") -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return filetext, text end local function cleanupText(text, leadOnly, keepRefs) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if it's the start of the article (blank lead) end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits if mw.ustring.find(text, "[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]") then -- avoid expensive search if possible text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text between onlyinclude sections text = mw.ustring.gsub(text, "^.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text before first onlyinclude section text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.*", "") -- remove text after last onlyinclude section end if not keepRefs then text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references end text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Remove unmatched <tag> or </tag> tags local function fixtags(text, tag) local startcount = 0 for i in mw.ustring.gmatch(text, "<%s*" .. tag .. "%f[^%w_].->") do startcount = startcount + 1 end local endcount = 0 for i in mw.ustring.gmatch(text, "<%s*/" .. tag .. "%f[^%w_].->") do endcount = endcount + 1 end if startcount > endcount then -- more <tag> than </tag>: remove the last few <tag>s local i = 0 text = mw.ustring.gsub(text, "<%s*" .. tag .. "%f[^%w_].->", function(t) i = i + 1 if i > endcount then return "" else return nil end end) -- "end" here terminates the anonymous replacement function(t) passed to gsub elseif endcount > startcount then -- more </tag> than <tag>: remove the first few </tag>s text = mw.ustring.gsub(text, "<%s*/" .. tag .. "%f[^%w_].->", "", endcount - startcount) end return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted local gotopt local pageoptstr -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then -- We have page or [[page]] or [[page|text]], possibly followed by |opt1|opt2... local pn pn, gotopt, pageoptstr = mw.ustring.match(pagename, "^%s*(%[%b[]%])%s*(|?)(.*)") if pn then pagename = mw.ustring.match(pn, "%[%[([^|%]]*)") -- turn [[page|text]] into page, discarding text else -- we have page or page|opt... pagename, gotopt, pageoptstr = mw.ustring.match(pagename, "%s*([^|]*[^|%s])%s*(|?)(.*)") end if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end local keepRefs if options.keepRefs == '1' then keepRefs = true else keepRefs = false end text = cleanupText(text, true, keepRefs) local pageopts = {} -- pageopts (even if value is "") have priority over global options for k, v in pairs(options) do pageopts[k] = v end if gotopt and gotopt ~= "" then for _, t in pairs(mw.text.split(pageoptstr, "|")) do local k, v = mw.ustring.match(t, "%s*([^=]-)%s*=(.-)%s*$") pageopts[k] = v end pageopts.paraflags = numberflags(pageopts["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} pageopts.fileflags = numberflags(pageopts["files"] or "") -- parse file numbers if pageopts.more and pageopts.more == "" then pageopts.more = "Read more..." end -- more= is short for this default text end local filetext filetext, text = parse(text, pageopts) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "()'''(.-'*)'''", function(a, b) if a < 100 and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return "'''[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]]''' else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end text = filetext .. text -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27") -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2") -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27") until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", "") -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", "") -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", "") -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. -- Ensure div tags match text = fixtags(text, "div") if pageopts.more then text = text .. " '''[[" .. pagename .. "|" .. pageopts.more .. "]]'''" end -- wikilink to article for more info if pageopts.list then -- add a collapsed list of pages which might appear local listtext = pageopts.list if listtext == "" then listtext = "Other articles" end text = text .. "{{collapse top|title={{resize|85%|" ..listtext .. "}}|bg=fff}}{{hlist" for _, p in pairs(pagenames) do if mw.ustring.match(p, "%S") then text = text .. "|[[" .. mw.text.trim(p) .. "]]" end end text = text .. "}}\n{{collapse bottom}}" end return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = "" if options.showall then local separator = "" for _, p in pairs(pagenames) do local t = main({ p }, options) if t ~= "" then text = text .. separator .. t separator = options.showall if separator == "" then separator = "{{clear}}{{hr}}" end end end else text = main(pagenames, options) end return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly, keepRefs) return cleanupText(text, leadOnly, keepRefs) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p f16jq11artt2lcseaqxbuq7kwheo14d 797318 797317 2020-02-05T11:30:57Z en>Certes 0 Remove lowercase {{notetag}} to fix [[Integer]] in [[Portal:Arithmetic]] 797318 Scribunto text/plain -- Local aliases of the file namespace local fileNamespaces = { "[Ff]ile", "[Ii]mage" } -- The module keeps all inline templates except these ones local unwantedInlineTemplates = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "[Nn]ote[Tt]ag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", "[Ii]nflation[ _/-]*[Ff]n", "[Bb]iblesource", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note", -- Explain and others "[Ee]xplain", "[Ff]urther[ ]*explanation[ ]*needed", "[Ee]laboration[ ]*needed", "[Ee]xplanation[ ]*needed", -- TOC templates "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Tt][Oo][Cc][8]*[5]*", "[Tt][Oo][Cc]", "09[Aa][Zz]", "[Tt][Oo][Cc][ ]*[Cc][Oo][Mm][Pp][Aa][Cc][Tt]", "[Tt][Oo][Cc][ ]*[Ss][Mm][Aa][Ll][Ll]", "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Aa][Ll][Pp][Hh][Aa][Bb][Ee][Tt][Ii][Cc][ _]*[Tt][Oo][Cc]", "DEFAULTSORT:.-", "[Oo]ne[ _]+source" } -- The module removes all block templates except these ones local wantedBlockTemplates = { "[Hh]istorical populations" } local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Help gsub to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchany(t, "^{{%s*", unwantedInlineTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noref = mw.ustring.gsub(noref, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end return title:getContent(), redir or title.prefixedText end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", fileNamespaces, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc, rtitle = getContent(page) -- get file description and title after following any redirect if desc and desc ~= "" then -- found description on local wiki if mw.ustring.match(desc, "[Nn]on%-free") then return false end desc = mw.ustring.gsub(desc, "%b{}", striptemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess elseif not rtitle then return false else -- try commons desc = "{{" .. rtitle .. "}}" end frame = frame or mw.getCurrentFrame() desc = frame:preprocess(desc) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", fileNamespaces, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position) if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", fileNamespaces, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", fileNamespaces, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end local imageArgGroups = { {"thumb", "thumbnail", "frame", "framed", "frameless"}, {"right", "left", "center", "none"}, {"baseline", "middle", "sub", "super", "text-top", "text-bottom", "top", "bottom"} } local function modifyImage(image, fileargs) if fileargs then for _, filearg in pairs(mw.text.split(fileargs, "|")) do -- handle fileargs=left|border etc. local fa = mw.ustring.gsub(filearg, "=.*", "") -- "upright=0.75" → "upright" local group = {fa} -- group of "border" is ["border"]... for _, g in pairs(imageArgGroups) do for _, a in pairs(g) do if fa == a then group = g end -- ...but group of "left" is ["right", "left", "center", "none"] end end for _, a in pairs(group) do image = mw.ustring.gsub(image, "|%s*" .. a .. "%f[%A]%s*=[^|%]]*", "") -- remove "|upright=0.75" etc. image = mw.ustring.gsub(image, "|%s*" .. a .. "%s*([|%]])", "%1") -- replace "|left|" by "|" etc. end image = mw.ustring.gsub(image, "([|%]])", "|" .. filearg .. "%1", 1) -- replace "|" by "|left|" etc. end end return image end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space -- Add named files local f = options.files if f and mw.ustring.match(f, "[^%d%s%-,]") then -- filename rather than number list f = mw.ustring.gsub(f, "^%s*File%s*:%s*", "", 1) f = mw.ustring.gsub(f, "^%s*Image%s*:%s*", "", 1) f = "[[File:" .. f .. "]]" f = modifyImage(f, "thumb") f = modifyImage(f, fileargs) if checkimage(f) then filetext = filetext .. f .. "\n" end end repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} or {| Table |} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif matchany(token, "{{%s*", wantedBlockTemplates, "%s*%f[|}]") then t = t .. token -- keep wanted block templates elseif options.keepTables and mw.ustring.sub(token, 1, 2) == '{|' then t = t .. token -- keep tables elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = modifyImage(image, "thumb") image = modifyImage(image, fileargs) filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original image = modifyImage(image, fileargs) filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$") -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return filetext, text end local function cleanupText(text, leadOnly, keepRefs) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if it's the start of the article (blank lead) end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits if mw.ustring.find(text, "[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]") then -- avoid expensive search if possible text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text between onlyinclude sections text = mw.ustring.gsub(text, "^.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text before first onlyinclude section text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.*", "") -- remove text after last onlyinclude section end if not keepRefs then text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references end text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Remove unmatched <tag> or </tag> tags local function fixtags(text, tag) local startcount = 0 for i in mw.ustring.gmatch(text, "<%s*" .. tag .. "%f[^%w_].->") do startcount = startcount + 1 end local endcount = 0 for i in mw.ustring.gmatch(text, "<%s*/" .. tag .. "%f[^%w_].->") do endcount = endcount + 1 end if startcount > endcount then -- more <tag> than </tag>: remove the last few <tag>s local i = 0 text = mw.ustring.gsub(text, "<%s*" .. tag .. "%f[^%w_].->", function(t) i = i + 1 if i > endcount then return "" else return nil end end) -- "end" here terminates the anonymous replacement function(t) passed to gsub elseif endcount > startcount then -- more </tag> than <tag>: remove the first few </tag>s text = mw.ustring.gsub(text, "<%s*/" .. tag .. "%f[^%w_].->", "", endcount - startcount) end return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted local gotopt local pageoptstr -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then -- We have page or [[page]] or [[page|text]], possibly followed by |opt1|opt2... local pn pn, gotopt, pageoptstr = mw.ustring.match(pagename, "^%s*(%[%b[]%])%s*(|?)(.*)") if pn then pagename = mw.ustring.match(pn, "%[%[([^|%]]*)") -- turn [[page|text]] into page, discarding text else -- we have page or page|opt... pagename, gotopt, pageoptstr = mw.ustring.match(pagename, "%s*([^|]*[^|%s])%s*(|?)(.*)") end if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end local keepRefs if options.keepRefs == '1' then keepRefs = true else keepRefs = false end text = cleanupText(text, true, keepRefs) local pageopts = {} -- pageopts (even if value is "") have priority over global options for k, v in pairs(options) do pageopts[k] = v end if gotopt and gotopt ~= "" then for _, t in pairs(mw.text.split(pageoptstr, "|")) do local k, v = mw.ustring.match(t, "%s*([^=]-)%s*=(.-)%s*$") pageopts[k] = v end pageopts.paraflags = numberflags(pageopts["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} pageopts.fileflags = numberflags(pageopts["files"] or "") -- parse file numbers if pageopts.more and pageopts.more == "" then pageopts.more = "Read more..." end -- more= is short for this default text end local filetext filetext, text = parse(text, pageopts) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "()'''(.-'*)'''", function(a, b) if a < 100 and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return "'''[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]]''' else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end text = filetext .. text -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27") -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2") -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27") until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", "") -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", "") -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", "") -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. -- Ensure div tags match text = fixtags(text, "div") if pageopts.more then text = text .. " '''[[" .. pagename .. "|" .. pageopts.more .. "]]'''" end -- wikilink to article for more info if pageopts.list then -- add a collapsed list of pages which might appear local listtext = pageopts.list if listtext == "" then listtext = "Other articles" end text = text .. "{{collapse top|title={{resize|85%|" ..listtext .. "}}|bg=fff}}{{hlist" for _, p in pairs(pagenames) do if mw.ustring.match(p, "%S") then text = text .. "|[[" .. mw.text.trim(p) .. "]]" end end text = text .. "}}\n{{collapse bottom}}" end return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = "" if options.showall then local separator = "" for _, p in pairs(pagenames) do local t = main({ p }, options) if t ~= "" then text = text .. separator .. t separator = options.showall if separator == "" then separator = "{{clear}}{{hr}}" end end end else text = main(pagenames, options) end return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly, keepRefs) return cleanupText(text, leadOnly, keepRefs) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p 1kx6jdeac2u338ygixqrm45d29f3dyp 797319 797318 2020-02-13T12:12:58Z en>Certes 0 Omit unnecessary list if showing all excerpts 797319 Scribunto text/plain -- Local aliases of the file namespace local fileNamespaces = { "[Ff]ile", "[Ii]mage" } -- The module keeps all inline templates except these ones local unwantedInlineTemplates = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "[Nn]ote[Tt]ag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", "[Ii]nflation[ _/-]*[Ff]n", "[Bb]iblesource", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note", -- Explain and others "[Ee]xplain", "[Ff]urther[ ]*explanation[ ]*needed", "[Ee]laboration[ ]*needed", "[Ee]xplanation[ ]*needed", -- TOC templates "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Tt][Oo][Cc][8]*[5]*", "[Tt][Oo][Cc]", "09[Aa][Zz]", "[Tt][Oo][Cc][ ]*[Cc][Oo][Mm][Pp][Aa][Cc][Tt]", "[Tt][Oo][Cc][ ]*[Ss][Mm][Aa][Ll][Ll]", "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Aa][Ll][Pp][Hh][Aa][Bb][Ee][Tt][Ii][Cc][ _]*[Tt][Oo][Cc]", "DEFAULTSORT:.-", "[Oo]ne[ _]+source" } -- The module removes all block templates except these ones local wantedBlockTemplates = { "[Hh]istorical populations" } local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Help gsub to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchany(t, "^{{%s*", unwantedInlineTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noref = mw.ustring.gsub(noref, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end return title:getContent(), redir or title.prefixedText end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", fileNamespaces, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc, rtitle = getContent(page) -- get file description and title after following any redirect if desc and desc ~= "" then -- found description on local wiki if mw.ustring.match(desc, "[Nn]on%-free") then return false end desc = mw.ustring.gsub(desc, "%b{}", striptemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess elseif not rtitle then return false else -- try commons desc = "{{" .. rtitle .. "}}" end frame = frame or mw.getCurrentFrame() desc = frame:preprocess(desc) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", fileNamespaces, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position) if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", fileNamespaces, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", fileNamespaces, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end local imageArgGroups = { {"thumb", "thumbnail", "frame", "framed", "frameless"}, {"right", "left", "center", "none"}, {"baseline", "middle", "sub", "super", "text-top", "text-bottom", "top", "bottom"} } local function modifyImage(image, fileargs) if fileargs then for _, filearg in pairs(mw.text.split(fileargs, "|")) do -- handle fileargs=left|border etc. local fa = mw.ustring.gsub(filearg, "=.*", "") -- "upright=0.75" → "upright" local group = {fa} -- group of "border" is ["border"]... for _, g in pairs(imageArgGroups) do for _, a in pairs(g) do if fa == a then group = g end -- ...but group of "left" is ["right", "left", "center", "none"] end end for _, a in pairs(group) do image = mw.ustring.gsub(image, "|%s*" .. a .. "%f[%A]%s*=[^|%]]*", "") -- remove "|upright=0.75" etc. image = mw.ustring.gsub(image, "|%s*" .. a .. "%s*([|%]])", "%1") -- replace "|left|" by "|" etc. end image = mw.ustring.gsub(image, "([|%]])", "|" .. filearg .. "%1", 1) -- replace "|" by "|left|" etc. end end return image end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space -- Add named files local f = options.files if f and mw.ustring.match(f, "[^%d%s%-,]") then -- filename rather than number list f = mw.ustring.gsub(f, "^%s*File%s*:%s*", "", 1) f = mw.ustring.gsub(f, "^%s*Image%s*:%s*", "", 1) f = "[[File:" .. f .. "]]" f = modifyImage(f, "thumb") f = modifyImage(f, fileargs) if checkimage(f) then filetext = filetext .. f .. "\n" end end repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} or {| Table |} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif matchany(token, "{{%s*", wantedBlockTemplates, "%s*%f[|}]") then t = t .. token -- keep wanted block templates elseif options.keepTables and mw.ustring.sub(token, 1, 2) == '{|' then t = t .. token -- keep tables elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = modifyImage(image, "thumb") image = modifyImage(image, fileargs) filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original image = modifyImage(image, fileargs) filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$") -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return filetext, text end local function cleanupText(text, leadOnly, keepRefs) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if it's the start of the article (blank lead) end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits if mw.ustring.find(text, "[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]") then -- avoid expensive search if possible text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text between onlyinclude sections text = mw.ustring.gsub(text, "^.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text before first onlyinclude section text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.*", "") -- remove text after last onlyinclude section end if not keepRefs then text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references end text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Remove unmatched <tag> or </tag> tags local function fixtags(text, tag) local startcount = 0 for i in mw.ustring.gmatch(text, "<%s*" .. tag .. "%f[^%w_].->") do startcount = startcount + 1 end local endcount = 0 for i in mw.ustring.gmatch(text, "<%s*/" .. tag .. "%f[^%w_].->") do endcount = endcount + 1 end if startcount > endcount then -- more <tag> than </tag>: remove the last few <tag>s local i = 0 text = mw.ustring.gsub(text, "<%s*" .. tag .. "%f[^%w_].->", function(t) i = i + 1 if i > endcount then return "" else return nil end end) -- "end" here terminates the anonymous replacement function(t) passed to gsub elseif endcount > startcount then -- more </tag> than <tag>: remove the first few </tag>s text = mw.ustring.gsub(text, "<%s*/" .. tag .. "%f[^%w_].->", "", endcount - startcount) end return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted local gotopt local pageoptstr -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then -- We have page or [[page]] or [[page|text]], possibly followed by |opt1|opt2... local pn pn, gotopt, pageoptstr = mw.ustring.match(pagename, "^%s*(%[%b[]%])%s*(|?)(.*)") if pn then pagename = mw.ustring.match(pn, "%[%[([^|%]]*)") -- turn [[page|text]] into page, discarding text else -- we have page or page|opt... pagename, gotopt, pageoptstr = mw.ustring.match(pagename, "%s*([^|]*[^|%s])%s*(|?)(.*)") end if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end local keepRefs if options.keepRefs == '1' then keepRefs = true else keepRefs = false end text = cleanupText(text, true, keepRefs) local pageopts = {} -- pageopts (even if value is "") have priority over global options for k, v in pairs(options) do pageopts[k] = v end if gotopt and gotopt ~= "" then for _, t in pairs(mw.text.split(pageoptstr, "|")) do local k, v = mw.ustring.match(t, "%s*([^=]-)%s*=(.-)%s*$") pageopts[k] = v end pageopts.paraflags = numberflags(pageopts["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} pageopts.fileflags = numberflags(pageopts["files"] or "") -- parse file numbers if pageopts.more and pageopts.more == "" then pageopts.more = "Read more..." end -- more= is short for this default text end local filetext filetext, text = parse(text, pageopts) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "()'''(.-'*)'''", function(a, b) if a < 100 and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return "'''[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]]''' else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end text = filetext .. text -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27") -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2") -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27") until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", "") -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", "") -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", "") -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. -- Ensure div tags match text = fixtags(text, "div") if pageopts.more then text = text .. " '''[[" .. pagename .. "|" .. pageopts.more .. "]]'''" end -- wikilink to article for more info if pageopts.list and not pageopts.showall then -- add a collapsed list of pages which might appear local listtext = pageopts.list if listtext == "" then listtext = "Other articles" end text = text .. "{{collapse top|title={{resize|85%|" ..listtext .. "}}|bg=fff}}{{hlist" for _, p in pairs(pagenames) do if mw.ustring.match(p, "%S") then text = text .. "|[[" .. mw.text.trim(p) .. "]]" end end text = text .. "}}\n{{collapse bottom}}" end return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = "" if options.showall then local separator = "" for _, p in pairs(pagenames) do local t = main({ p }, options) if t ~= "" then text = text .. separator .. t separator = options.showall if separator == "" then separator = "{{clear}}{{hr}}" end end end else text = main(pagenames, options) end return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly, keepRefs) return cleanupText(text, leadOnly, keepRefs) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p pjs5jvuatg5t94omdq0wafabypeiiso 797320 797319 2020-04-01T17:34:48Z en>Sophivorus 0 Check that the section title is not empty 797320 Scribunto text/plain -- Local aliases of the file namespace local fileNamespaces = { "[Ff]ile", "[Ii]mage" } -- The module keeps all inline templates except these ones local unwantedInlineTemplates = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "[Nn]ote[Tt]ag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", "[Ii]nflation[ _/-]*[Ff]n", "[Bb]iblesource", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note", -- Explain and others "[Ee]xplain", "[Ff]urther[ ]*explanation[ ]*needed", "[Ee]laboration[ ]*needed", "[Ee]xplanation[ ]*needed", -- TOC templates "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Tt][Oo][Cc][8]*[5]*", "[Tt][Oo][Cc]", "09[Aa][Zz]", "[Tt][Oo][Cc][ ]*[Cc][Oo][Mm][Pp][Aa][Cc][Tt]", "[Tt][Oo][Cc][ ]*[Ss][Mm][Aa][Ll][Ll]", "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Aa][Ll][Pp][Hh][Aa][Bb][Ee][Tt][Ii][Cc][ _]*[Tt][Oo][Cc]", "DEFAULTSORT:.-", "[Oo]ne[ _]+source" } -- The module removes all block templates except these ones local wantedBlockTemplates = { "[Hh]istorical populations" } local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Help gsub to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchany(t, "^{{%s*", unwantedInlineTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noref = mw.ustring.gsub(noref, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end return title:getContent(), redir or title.prefixedText end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", fileNamespaces, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc, rtitle = getContent(page) -- get file description and title after following any redirect if desc and desc ~= "" then -- found description on local wiki if mw.ustring.match(desc, "[Nn]on%-free") then return false end desc = mw.ustring.gsub(desc, "%b{}", striptemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess elseif not rtitle then return false else -- try commons desc = "{{" .. rtitle .. "}}" end frame = frame or mw.getCurrentFrame() desc = frame:preprocess(desc) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", fileNamespaces, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position) if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", fileNamespaces, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", fileNamespaces, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end local imageArgGroups = { {"thumb", "thumbnail", "frame", "framed", "frameless"}, {"right", "left", "center", "none"}, {"baseline", "middle", "sub", "super", "text-top", "text-bottom", "top", "bottom"} } local function modifyImage(image, fileargs) if fileargs then for _, filearg in pairs(mw.text.split(fileargs, "|")) do -- handle fileargs=left|border etc. local fa = mw.ustring.gsub(filearg, "=.*", "") -- "upright=0.75" → "upright" local group = {fa} -- group of "border" is ["border"]... for _, g in pairs(imageArgGroups) do for _, a in pairs(g) do if fa == a then group = g end -- ...but group of "left" is ["right", "left", "center", "none"] end end for _, a in pairs(group) do image = mw.ustring.gsub(image, "|%s*" .. a .. "%f[%A]%s*=[^|%]]*", "") -- remove "|upright=0.75" etc. image = mw.ustring.gsub(image, "|%s*" .. a .. "%s*([|%]])", "%1") -- replace "|left|" by "|" etc. end image = mw.ustring.gsub(image, "([|%]])", "|" .. filearg .. "%1", 1) -- replace "|" by "|left|" etc. end end return image end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space -- Add named files local f = options.files if f and mw.ustring.match(f, "[^%d%s%-,]") then -- filename rather than number list f = mw.ustring.gsub(f, "^%s*File%s*:%s*", "", 1) f = mw.ustring.gsub(f, "^%s*Image%s*:%s*", "", 1) f = "[[File:" .. f .. "]]" f = modifyImage(f, "thumb") f = modifyImage(f, fileargs) if checkimage(f) then filetext = filetext .. f .. "\n" end end repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} or {| Table |} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif matchany(token, "{{%s*", wantedBlockTemplates, "%s*%f[|}]") then t = t .. token -- keep wanted block templates elseif options.keepTables and mw.ustring.sub(token, 1, 2) == '{|' then t = t .. token -- keep tables elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = modifyImage(image, "thumb") image = modifyImage(image, fileargs) filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original image = modifyImage(image, fileargs) filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$") -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return filetext, text end local function cleanupText(text, leadOnly, keepRefs) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if it's the start of the article (blank lead) end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits if mw.ustring.find(text, "[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]") then -- avoid expensive search if possible text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text between onlyinclude sections text = mw.ustring.gsub(text, "^.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text before first onlyinclude section text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.*", "") -- remove text after last onlyinclude section end if not keepRefs then text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references end text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Remove unmatched <tag> or </tag> tags local function fixtags(text, tag) local startcount = 0 for i in mw.ustring.gmatch(text, "<%s*" .. tag .. "%f[^%w_].->") do startcount = startcount + 1 end local endcount = 0 for i in mw.ustring.gmatch(text, "<%s*/" .. tag .. "%f[^%w_].->") do endcount = endcount + 1 end if startcount > endcount then -- more <tag> than </tag>: remove the last few <tag>s local i = 0 text = mw.ustring.gsub(text, "<%s*" .. tag .. "%f[^%w_].->", function(t) i = i + 1 if i > endcount then return "" else return nil end end) -- "end" here terminates the anonymous replacement function(t) passed to gsub elseif endcount > startcount then -- more </tag> than <tag>: remove the first few </tag>s text = mw.ustring.gsub(text, "<%s*/" .. tag .. "%f[^%w_].->", "", endcount - startcount) end return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted local gotopt local pageoptstr -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then -- We have page or [[page]] or [[page|text]], possibly followed by |opt1|opt2... local pn pn, gotopt, pageoptstr = mw.ustring.match(pagename, "^%s*(%[%b[]%])%s*(|?)(.*)") if pn then pagename = mw.ustring.match(pn, "%[%[([^|%]]*)") -- turn [[page|text]] into page, discarding text else -- we have page or page|opt... pagename, gotopt, pageoptstr = mw.ustring.match(pagename, "%s*([^|]*[^|%s])%s*(|?)(.*)") end if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section and section ~= "" then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end local keepRefs if options.keepRefs == '1' then keepRefs = true else keepRefs = false end text = cleanupText(text, true, keepRefs) local pageopts = {} -- pageopts (even if value is "") have priority over global options for k, v in pairs(options) do pageopts[k] = v end if gotopt and gotopt ~= "" then for _, t in pairs(mw.text.split(pageoptstr, "|")) do local k, v = mw.ustring.match(t, "%s*([^=]-)%s*=(.-)%s*$") pageopts[k] = v end pageopts.paraflags = numberflags(pageopts["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} pageopts.fileflags = numberflags(pageopts["files"] or "") -- parse file numbers if pageopts.more and pageopts.more == "" then pageopts.more = "Read more..." end -- more= is short for this default text end local filetext filetext, text = parse(text, pageopts) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "()'''(.-'*)'''", function(a, b) if a < 100 and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return "'''[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]]''' else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end text = filetext .. text -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27") -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2") -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27") until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", "") -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", "") -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", "") -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. -- Ensure div tags match text = fixtags(text, "div") if pageopts.more then text = text .. " '''[[" .. pagename .. "|" .. pageopts.more .. "]]'''" end -- wikilink to article for more info if pageopts.list and not pageopts.showall then -- add a collapsed list of pages which might appear local listtext = pageopts.list if listtext == "" then listtext = "Other articles" end text = text .. "{{collapse top|title={{resize|85%|" ..listtext .. "}}|bg=fff}}{{hlist" for _, p in pairs(pagenames) do if mw.ustring.match(p, "%S") then text = text .. "|[[" .. mw.text.trim(p) .. "]]" end end text = text .. "}}\n{{collapse bottom}}" end return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = "" if options.showall then local separator = "" for _, p in pairs(pagenames) do local t = main({ p }, options) if t ~= "" then text = text .. separator .. t separator = options.showall if separator == "" then separator = "{{clear}}{{hr}}" end end end else text = main(pagenames, options) end return frame:preprocess(text) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly, keepRefs) return cleanupText(text, leadOnly, keepRefs) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p o96m8buf6soqw2uvjuaes48vbij1m82 797321 797320 2020-04-01T20:59:27Z en>Sophivorus 0 Optionally add articles with broken excerpts to a tracking category 797321 Scribunto text/plain -- Local aliases of the file namespace local fileNamespaces = { "[Ff]ile", "[Ii]mage" } -- Local category to track content pages with broken excerpts (may be empty, don't include the "Category:" prefix) local brokenCategory = "Articles with broken excerpts" -- The module keeps all inline templates except these ones local unwantedInlineTemplates = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "[Nn]ote[Tt]ag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", "[Ii]nflation[ _/-]*[Ff]n", "[Bb]iblesource", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note", -- Explain and others "[Ee]xplain", "[Ff]urther[ ]*explanation[ ]*needed", "[Ee]laboration[ ]*needed", "[Ee]xplanation[ ]*needed", -- TOC templates "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Tt][Oo][Cc][8]*[5]*", "[Tt][Oo][Cc]", "09[Aa][Zz]", "[Tt][Oo][Cc][ ]*[Cc][Oo][Mm][Pp][Aa][Cc][Tt]", "[Tt][Oo][Cc][ ]*[Ss][Mm][Aa][Ll][Ll]", "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Aa][Ll][Pp][Hh][Aa][Bb][Ee][Tt][Ii][Cc][ _]*[Tt][Oo][Cc]", "DEFAULTSORT:.-", "[Oo]ne[ _]+source" } -- The module removes all block templates except these ones local wantedBlockTemplates = { "[Hh]istorical populations" } local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Help gsub to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchany(t, "^{{%s*", unwantedInlineTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noref = mw.ustring.gsub(noref, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end return title:getContent(), redir or title.prefixedText end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", fileNamespaces, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc, rtitle = getContent(page) -- get file description and title after following any redirect if desc and desc ~= "" then -- found description on local wiki if mw.ustring.match(desc, "[Nn]on%-free") then return false end desc = mw.ustring.gsub(desc, "%b{}", striptemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess elseif not rtitle then return false else -- try commons desc = "{{" .. rtitle .. "}}" end frame = frame or mw.getCurrentFrame() desc = frame:preprocess(desc) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", fileNamespaces, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position) if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", fileNamespaces, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", fileNamespaces, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end local imageArgGroups = { {"thumb", "thumbnail", "frame", "framed", "frameless"}, {"right", "left", "center", "none"}, {"baseline", "middle", "sub", "super", "text-top", "text-bottom", "top", "bottom"} } local function modifyImage(image, fileargs) if fileargs then for _, filearg in pairs(mw.text.split(fileargs, "|")) do -- handle fileargs=left|border etc. local fa = mw.ustring.gsub(filearg, "=.*", "") -- "upright=0.75" → "upright" local group = {fa} -- group of "border" is ["border"]... for _, g in pairs(imageArgGroups) do for _, a in pairs(g) do if fa == a then group = g end -- ...but group of "left" is ["right", "left", "center", "none"] end end for _, a in pairs(group) do image = mw.ustring.gsub(image, "|%s*" .. a .. "%f[%A]%s*=[^|%]]*", "") -- remove "|upright=0.75" etc. image = mw.ustring.gsub(image, "|%s*" .. a .. "%s*([|%]])", "%1") -- replace "|left|" by "|" etc. end image = mw.ustring.gsub(image, "([|%]])", "|" .. filearg .. "%1", 1) -- replace "|" by "|left|" etc. end end return image end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space -- Add named files local f = options.files if f and mw.ustring.match(f, "[^%d%s%-,]") then -- filename rather than number list f = mw.ustring.gsub(f, "^%s*File%s*:%s*", "", 1) f = mw.ustring.gsub(f, "^%s*Image%s*:%s*", "", 1) f = "[[File:" .. f .. "]]" f = modifyImage(f, "thumb") f = modifyImage(f, fileargs) if checkimage(f) then filetext = filetext .. f .. "\n" end end repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} or {| Table |} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif matchany(token, "{{%s*", wantedBlockTemplates, "%s*%f[|}]") then t = t .. token -- keep wanted block templates elseif options.keepTables and mw.ustring.sub(token, 1, 2) == '{|' then t = t .. token -- keep tables elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = modifyImage(image, "thumb") image = modifyImage(image, fileargs) filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original image = modifyImage(image, fileargs) filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$") -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return filetext, text end local function cleanupText(text, leadOnly, keepRefs) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if it's the start of the article (blank lead) end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits if mw.ustring.find(text, "[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]") then -- avoid expensive search if possible text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text between onlyinclude sections text = mw.ustring.gsub(text, "^.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text before first onlyinclude section text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.*", "") -- remove text after last onlyinclude section end if not keepRefs then text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references end text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Remove unmatched <tag> or </tag> tags local function fixtags(text, tag) local startcount = 0 for i in mw.ustring.gmatch(text, "<%s*" .. tag .. "%f[^%w_].->") do startcount = startcount + 1 end local endcount = 0 for i in mw.ustring.gmatch(text, "<%s*/" .. tag .. "%f[^%w_].->") do endcount = endcount + 1 end if startcount > endcount then -- more <tag> than </tag>: remove the last few <tag>s local i = 0 text = mw.ustring.gsub(text, "<%s*" .. tag .. "%f[^%w_].->", function(t) i = i + 1 if i > endcount then return "" else return nil end end) -- "end" here terminates the anonymous replacement function(t) passed to gsub elseif endcount > startcount then -- more </tag> than <tag>: remove the first few </tag>s text = mw.ustring.gsub(text, "<%s*/" .. tag .. "%f[^%w_].->", "", endcount - startcount) end return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted local gotopt local pageoptstr -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then -- We have page or [[page]] or [[page|text]], possibly followed by |opt1|opt2... local pn pn, gotopt, pageoptstr = mw.ustring.match(pagename, "^%s*(%[%b[]%])%s*(|?)(.*)") if pn then pagename = mw.ustring.match(pn, "%[%[([^|%]]*)") -- turn [[page|text]] into page, discarding text else -- we have page or page|opt... pagename, gotopt, pageoptstr = mw.ustring.match(pagename, "%s*([^|]*[^|%s])%s*(|?)(.*)") end if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section and section ~= "" then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end local keepRefs if options.keepRefs == '1' then keepRefs = true else keepRefs = false end text = cleanupText(text, true, keepRefs) local pageopts = {} -- pageopts (even if value is "") have priority over global options for k, v in pairs(options) do pageopts[k] = v end if gotopt and gotopt ~= "" then for _, t in pairs(mw.text.split(pageoptstr, "|")) do local k, v = mw.ustring.match(t, "%s*([^=]-)%s*=(.-)%s*$") pageopts[k] = v end pageopts.paraflags = numberflags(pageopts["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} pageopts.fileflags = numberflags(pageopts["files"] or "") -- parse file numbers if pageopts.more and pageopts.more == "" then pageopts.more = "Read more..." end -- more= is short for this default text end local filetext filetext, text = parse(text, pageopts) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "()'''(.-'*)'''", function(a, b) if a < 100 and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return "'''[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]]''' else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end text = filetext .. text -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27") -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2") -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27") until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", "") -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", "") -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", "") -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. -- Ensure div tags match text = fixtags(text, "div") if pageopts.more then text = text .. " '''[[" .. pagename .. "|" .. pageopts.more .. "]]'''" end -- wikilink to article for more info if pageopts.list and not pageopts.showall then -- add a collapsed list of pages which might appear local listtext = pageopts.list if listtext == "" then listtext = "Other articles" end text = text .. "{{collapse top|title={{resize|85%|" ..listtext .. "}}|bg=fff}}{{hlist" for _, p in pairs(pagenames) do if mw.ustring.match(p, "%S") then text = text .. "|[[" .. mw.text.trim(p) .. "]]" end end text = text .. "}}\n{{collapse bottom}}" end return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = "" if options.showall then local separator = "" for _, p in pairs(pagenames) do local t = main({ p }, options) if t ~= "" then text = text .. separator .. t separator = options.showall if separator == "" then separator = "{{clear}}{{hr}}" end end end else text = main(pagenames, options) end if text == "" and brokenCategory and brokenCategory ~= "" and mw.title.getCurrentTitle().isContentPage then return "[[Category:" .. brokenCategory .. "]]" else return frame:preprocess(text) end end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly, keepRefs) return cleanupText(text, leadOnly, keepRefs) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p en3hzq8w98iqp64ras69l4o9l45emjm 797322 797321 2020-04-03T22:12:36Z en>Sophivorus 0 Add option to remove all bold text from excerpt 797322 Scribunto text/plain -- Local aliases of the file namespace local fileNamespaces = { "[Ff]ile", "[Ii]mage" } -- Local category to track content pages with broken excerpts (may be empty, don't include the "Category:" prefix) local brokenCategory = "Articles with broken excerpts" -- The module keeps all inline templates except these ones local unwantedInlineTemplates = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "[Nn]ote[Tt]ag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", "[Ii]nflation[ _/-]*[Ff]n", "[Bb]iblesource", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note", -- Explain and others "[Ee]xplain", "[Ff]urther[ ]*explanation[ ]*needed", "[Ee]laboration[ ]*needed", "[Ee]xplanation[ ]*needed", -- TOC templates "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Tt][Oo][Cc][8]*[5]*", "[Tt][Oo][Cc]", "09[Aa][Zz]", "[Tt][Oo][Cc][ ]*[Cc][Oo][Mm][Pp][Aa][Cc][Tt]", "[Tt][Oo][Cc][ ]*[Ss][Mm][Aa][Ll][Ll]", "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Aa][Ll][Pp][Hh][Aa][Bb][Ee][Tt][Ii][Cc][ _]*[Tt][Oo][Cc]", "DEFAULTSORT:.-", "[Oo]ne[ _]+source" } -- The module removes all block templates except these ones local wantedBlockTemplates = { "[Hh]istorical populations" } local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post) local match for i = 1, #list do match = mw.ustring.match(text, pre .. list[i] .. post) if match then return match end end return nil end -- Help gsub to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchany(t, "^{{%s*", unwantedInlineTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noref = mw.ustring.gsub(noref, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end return title:getContent(), redir or title.prefixedText end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", fileNamespaces, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc, rtitle = getContent(page) -- get file description and title after following any redirect if desc and desc ~= "" then -- found description on local wiki if mw.ustring.match(desc, "[Nn]on%-free") then return false end desc = mw.ustring.gsub(desc, "%b{}", striptemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess elseif not rtitle then return false else -- try commons desc = "{{" .. rtitle .. "}}" end frame = frame or mw.getCurrentFrame() desc = frame:preprocess(desc) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", fileNamespaces, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = mw.ustring.match(text, "|%s*[^=|]*[Cc][Aa][Pp][Tt][Ii][Oo][Nn][^=|]*%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position) if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", fileNamespaces, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", fileNamespaces, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end local imageArgGroups = { {"thumb", "thumbnail", "frame", "framed", "frameless"}, {"right", "left", "center", "none"}, {"baseline", "middle", "sub", "super", "text-top", "text-bottom", "top", "bottom"} } local function modifyImage(image, fileargs) if fileargs then for _, filearg in pairs(mw.text.split(fileargs, "|")) do -- handle fileargs=left|border etc. local fa = mw.ustring.gsub(filearg, "=.*", "") -- "upright=0.75" → "upright" local group = {fa} -- group of "border" is ["border"]... for _, g in pairs(imageArgGroups) do for _, a in pairs(g) do if fa == a then group = g end -- ...but group of "left" is ["right", "left", "center", "none"] end end for _, a in pairs(group) do image = mw.ustring.gsub(image, "|%s*" .. a .. "%f[%A]%s*=[^|%]]*", "") -- remove "|upright=0.75" etc. image = mw.ustring.gsub(image, "|%s*" .. a .. "%s*([|%]])", "%1") -- replace "|left|" by "|" etc. end image = mw.ustring.gsub(image, "([|%]])", "|" .. filearg .. "%1", 1) -- replace "|" by "|left|" etc. end end return image end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space -- Add named files local f = options.files if f and mw.ustring.match(f, "[^%d%s%-,]") then -- filename rather than number list f = mw.ustring.gsub(f, "^%s*File%s*:%s*", "", 1) f = mw.ustring.gsub(f, "^%s*Image%s*:%s*", "", 1) f = "[[File:" .. f .. "]]" f = modifyImage(f, "thumb") f = modifyImage(f, fileargs) if checkimage(f) then filetext = filetext .. f .. "\n" end end repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} or {| Table |} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif matchany(token, "{{%s*", wantedBlockTemplates, "%s*%f[|}]") then t = t .. token -- keep wanted block templates elseif options.keepTables and mw.ustring.sub(token, 1, 2) == '{|' then t = t .. token -- keep tables elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = modifyImage(image, "thumb") image = modifyImage(image, fileargs) filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original image = modifyImage(image, fileargs) filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$") -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return filetext, text end local function cleanupText(text, leadOnly, keepRefs) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if it's the start of the article (blank lead) end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits if mw.ustring.find(text, "[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]") then -- avoid expensive search if possible text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text between onlyinclude sections text = mw.ustring.gsub(text, "^.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text before first onlyinclude section text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.*", "") -- remove text after last onlyinclude section end if not keepRefs then text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references end text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Remove unmatched <tag> or </tag> tags local function fixtags(text, tag) local startcount = 0 for i in mw.ustring.gmatch(text, "<%s*" .. tag .. "%f[^%w_].->") do startcount = startcount + 1 end local endcount = 0 for i in mw.ustring.gmatch(text, "<%s*/" .. tag .. "%f[^%w_].->") do endcount = endcount + 1 end if startcount > endcount then -- more <tag> than </tag>: remove the last few <tag>s local i = 0 text = mw.ustring.gsub(text, "<%s*" .. tag .. "%f[^%w_].->", function(t) i = i + 1 if i > endcount then return "" else return nil end end) -- "end" here terminates the anonymous replacement function(t) passed to gsub elseif endcount > startcount then -- more </tag> than <tag>: remove the first few </tag>s text = mw.ustring.gsub(text, "<%s*/" .. tag .. "%f[^%w_].->", "", endcount - startcount) end return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted local gotopt local pageoptstr -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then -- We have page or [[page]] or [[page|text]], possibly followed by |opt1|opt2... local pn pn, gotopt, pageoptstr = mw.ustring.match(pagename, "^%s*(%[%b[]%])%s*(|?)(.*)") if pn then pagename = mw.ustring.match(pn, "%[%[([^|%]]*)") -- turn [[page|text]] into page, discarding text else -- we have page or page|opt... pagename, gotopt, pageoptstr = mw.ustring.match(pagename, "%s*([^|]*[^|%s])%s*(|?)(.*)") end if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section and section ~= "" then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end local keepRefs if options.keepRefs == '1' then keepRefs = true else keepRefs = false end text = cleanupText(text, true, keepRefs) local pageopts = {} -- pageopts (even if value is "") have priority over global options for k, v in pairs(options) do pageopts[k] = v end if gotopt and gotopt ~= "" then for _, t in pairs(mw.text.split(pageoptstr, "|")) do local k, v = mw.ustring.match(t, "%s*([^=]-)%s*=(.-)%s*$") pageopts[k] = v end pageopts.paraflags = numberflags(pageopts["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} pageopts.fileflags = numberflags(pageopts["files"] or "") -- parse file numbers if pageopts.more and pageopts.more == "" then pageopts.more = "Read more..." end -- more= is short for this default text end local filetext filetext, text = parse(text, pageopts) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "()'''(.-'*)'''", function(a, b) if a < 100 and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return "'''[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]]''' else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- remove '''bold text''' if requested if pageopts.nobold then text = mw.ustring.gsub(text, "'''", "") end text = filetext .. text -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27") -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2") -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27") until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", "") -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", "") -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", "") -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. -- Ensure div tags match text = fixtags(text, "div") if pageopts.more then text = text .. " '''[[" .. pagename .. "|" .. pageopts.more .. "]]'''" end -- wikilink to article for more info if pageopts.list and not pageopts.showall then -- add a collapsed list of pages which might appear local listtext = pageopts.list if listtext == "" then listtext = "Other articles" end text = text .. "{{collapse top|title={{resize|85%|" ..listtext .. "}}|bg=fff}}{{hlist" for _, p in pairs(pagenames) do if mw.ustring.match(p, "%S") then text = text .. "|[[" .. mw.text.trim(p) .. "]]" end end text = text .. "}}\n{{collapse bottom}}" end return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = "" if options.showall then local separator = "" for _, p in pairs(pagenames) do local t = main({ p }, options) if t ~= "" then text = text .. separator .. t separator = options.showall if separator == "" then separator = "{{clear}}{{hr}}" end end end else text = main(pagenames, options) end if text == "" and brokenCategory and brokenCategory ~= "" and mw.title.getCurrentTitle().isContentPage then return "[[Category:" .. brokenCategory .. "]]" else return frame:preprocess(text) end end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly, keepRefs) return cleanupText(text, leadOnly, keepRefs) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p e7wjsa3vx6z7yqkbu8uabrov9seo48i 797323 797322 2020-04-04T19:38:50Z en>Sophivorus 0 Update to latest sandbox version 797323 Scribunto text/plain -- Local aliases of the file namespace local fileNamespaces = { "[Ff]ile", "[Ii]mage" } local captionParams = { "[^=|]*[Cc]aption[^=|]*", "[^=|]*[Ll]egend[^=|]*" } -- Local category to track content pages with broken excerpts (may be empty, don't include the "Category:" prefix) local brokenCategory = "Articles with broken excerpts" -- The module keeps all inline templates except these ones local unwantedInlineTemplates = {"[Ee]fn", "[Ee]fn%-[lu][arg]", "[Ee]l[mn]", "[Rr]p?", "[Ss]fn[bmp]", "[Ss]f[bn]", "[Nn]ote[Tt]ag", "#[Tt]ag:%s*[Rr]ef", "[Rr]efn?", "[CcDd]n", "[Cc]itation[%- _]needed", "[Dd]isambiguation needed", "[Ff]eatured article", "[Gg]ood article", "[Dd]ISPLAYTITLE", "[Ss]hort[ _]+description", "[Cc]itation", "[Cc]ite[%- _]+[%w_%s]-", "[Cc]oor[%w_%s]-", "[Uu]?n?[Rr]eliable source[%?%w_%s]-", "[Rr]s%??", "[Vv]c", "[Vv]erify credibility", "[Bb]y[ _]*[Ww]ho[m]*%??", "[Ww]ikisource[ -_]*multi", "[Ii]nflation[ _/-]*[Ff]n", "[Bb]iblesource", -- aliases for Clarification needed "[Cc]f[ny]", "[Cc]larification[ _]+inline", "[Cc]larification[%- _]*needed", "[Cc]larification", "[Cc]larify%-inline", "[Cc]larify%-?me", "[Cc]larify[ _]+inline", "[Cc]larify", "[Cc]LARIFY", "[Cc]onfusing%-inline", "[Cc]onfusing%-short", "[Ee]xplainme", "[Hh]uh[ _]*%??", "[Ww]hat%?", "[Ii]nline[ _]+[Uu]nclear", "[Ii]n[ _]+what[ _]+sense", "[Oo]bscure", "[Pp]lease[ _]+clarify", "[Uu]nclear[ _]+inline", "[Ww]hat's[ _]+this%?", "[Gg]eoQuelle", "[Nn]eed[s]+[%- _]+[Ii][Pp][Aa]", "[Ii]PA needed", -- aliases for Clarification needed lead "[Cc]itation needed %(?lea?de?%)?", "[Cc]nl", "[Ff]act %(?lea?de?%)?", "[Ll]ead citation needed", "[Nn]ot in body", "[Nn]ot verified in body", -- Primary source etc. "[Pp]s[ci]", "[Nn]psn", "[Nn]on%-primary[ _]+source[ _]+needed", "[Ss]elf%-published[%w_%s]-", "[Uu]ser%-generated[%w_%s]-", "[Pp]rimary source[%w_%s]-", "[Ss]econdary source[%w_%s]-", "[Tt]ertiary source[%w_%s]-", "[Tt]hird%-party[%w_%s]-", -- aliases for Disambiguation (page) and similar "[Bb]egriffsklärung", "[Dd][Aa][Bb]", "[Dd]big", "[%w_%s]-%f[%w][Dd]isam[%w_%s]-", "[Hh][Nn][Dd][Ii][Ss]", -- aliases for Failed verification "[Bb]adref", "[Ff]aile?[ds] ?[rv][%w_%s]-", "[Ff][Vv]", "[Nn][Ii]?[Cc][Gg]", "[Nn]ot ?in ?[crs][%w_%s]-", "[Nn]ot specifically in source", "[Vv]erification[%- _]failed", -- aliases for When "[Aa]s[ _]+of[ _]+when%??", "[Aa]s[ _%-]+of%??", "[Cc]larify date", "[Dd]ate[ _]*needed", "[Nn]eeds?[ _]+date", "[Rr]ecently", "[Ss]ince[ _]+when%??", "[Ww]HEN", "[Ww]hen%??", -- aliases for Update "[Nn]ot[ _]*up[ _]*to[ _]*date","[Oo]u?[Tt][Dd]","[Oo]ut[%- _]*o?f?[%- _]*dated?", "[Uu]pdate", "[Uu]pdate[ _]+sect", "[Uu]pdate[ _]+Watch", -- aliases for Pronunciation needed "[Pp]ronunciation%??[%- _]*n?e?e?d?e?d?", "[Pp]ronounce", "[Rr]equested[%- _]*pronunciation", "[Rr]e?q?pron", "[Nn]eeds[%- _]*pronunciation", -- Chart, including Chart/start etc. "[Cc]hart", "[Cc]hart/[%w_%s]-", -- Cref and others "[Cc]ref2?", "[Cc]note", -- Explain and others "[Ee]xplain", "[Ff]urther[ ]*explanation[ ]*needed", "[Ee]laboration[ ]*needed", "[Ee]xplanation[ ]*needed", -- TOC templates "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Tt][Oo][Cc][8]*[5]*", "[Tt][Oo][Cc]", "09[Aa][Zz]", "[Tt][Oo][Cc][ ]*[Cc][Oo][Mm][Pp][Aa][Cc][Tt]", "[Tt][Oo][Cc][ ]*[Ss][Mm][Aa][Ll][Ll]", "[Cc][Oo][Mm][Pp][Aa][Cc][Tt][ _]*[Aa][Ll][Pp][Hh][Aa][Bb][Ee][Tt][Ii][Cc][ _]*[Tt][Oo][Cc]", "DEFAULTSORT:.-", "[Oo]ne[ _]+source" } -- The module removes all block templates except these ones local wantedBlockTemplates = { "[Hh]istorical populations" } local p = {} local mRedirect = require('Module:Redirect') -- Get a redirect target (or nil if not a redirect) without using the expensive title object property .isRedirect local function getRedirectTarget(titleObject) local content = titleObject:getContent() if not content then return nil end return mRedirect.getTargetFromText(content) end local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post, init) local match = {} for i = 1, #list do match = { mw.ustring.match(text, pre .. list[i] .. post, init) } if match[1] then return unpack(match) end end return nil end -- Help gsub to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchany(t, "^{{%s*", unwantedInlineTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noref = mw.ustring.gsub(noref, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local redir = getRedirectTarget(title) if redir then title = mw.title.new(redir) end return title:getContent(), redir or title.prefixedText end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", fileNamespaces, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc, rtitle = getContent(page) -- get file description and title after following any redirect if desc and desc ~= "" then -- found description on local wiki if mw.ustring.match(desc, "[Nn]on%-free") then return false end desc = mw.ustring.gsub(desc, "%b{}", striptemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess elseif not rtitle then return false else -- try commons desc = "{{" .. rtitle .. "}}" end frame = frame or mw.getCurrentFrame() desc = frame:preprocess(desc) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", fileNamespaces, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end return caption -- No terminator found: return entire caption end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = matchany(text, "|%s*", captionParams, "%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position) if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", fileNamespaces, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", fileNamespaces, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end local imageArgGroups = { {"thumb", "thumbnail", "frame", "framed", "frameless"}, {"right", "left", "center", "none"}, {"baseline", "middle", "sub", "super", "text-top", "text-bottom", "top", "bottom"} } local function modifyImage(image, fileargs) if fileargs then for _, filearg in pairs(mw.text.split(fileargs, "|")) do -- handle fileargs=left|border etc. local fa = mw.ustring.gsub(filearg, "=.*", "") -- "upright=0.75" → "upright" local group = {fa} -- group of "border" is ["border"]... for _, g in pairs(imageArgGroups) do for _, a in pairs(g) do if fa == a then group = g end -- ...but group of "left" is ["right", "left", "center", "none"] end end for _, a in pairs(group) do image = mw.ustring.gsub(image, "|%s*" .. a .. "%f[%A]%s*=[^|%]]*", "") -- remove "|upright=0.75" etc. image = mw.ustring.gsub(image, "|%s*" .. a .. "%s*([|%]])", "%1") -- replace "|left|" by "|" etc. end image = mw.ustring.gsub(image, "([|%]])", "|" .. filearg .. "%1", 1) -- replace "|" by "|left|" etc. end end return image end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space -- Add named files local f = options.files if f and mw.ustring.match(f, "[^%d%s%-,]") then -- filename rather than number list f = mw.ustring.gsub(f, "^%s*File%s*:%s*", "", 1) f = mw.ustring.gsub(f, "^%s*Image%s*:%s*", "", 1) f = "[[File:" .. f .. "]]" f = modifyImage(f, "thumb") f = modifyImage(f, fileargs) if checkimage(f) then filetext = filetext .. f .. "\n" end end repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} or {| Table |} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif matchany(token, "{{%s*", wantedBlockTemplates, "%s*%f[|}]") then t = t .. token -- keep wanted block templates elseif options.keepTables and mw.ustring.sub(token, 1, 2) == '{|' then t = t .. token -- keep tables elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = modifyImage(image, "thumb") image = modifyImage(image, fileargs) filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original image = modifyImage(image, fileargs) filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$") -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return filetext, text end local function cleanupText(text, leadOnly, keepRefs) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if leadOnly then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if it's the start of the article (blank lead) end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits if mw.ustring.find(text, "[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]") then -- avoid expensive search if possible text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text between onlyinclude sections text = mw.ustring.gsub(text, "^.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text before first onlyinclude section text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.*", "") -- remove text after last onlyinclude section end if not keepRefs then text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references end text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Remove unmatched <tag> or </tag> tags local function fixtags(text, tag) local startcount = 0 for i in mw.ustring.gmatch(text, "<%s*" .. tag .. "%f[^%w_].->") do startcount = startcount + 1 end local endcount = 0 for i in mw.ustring.gmatch(text, "<%s*/" .. tag .. "%f[^%w_].->") do endcount = endcount + 1 end if startcount > endcount then -- more <tag> than </tag>: remove the last few <tag>s local i = 0 text = mw.ustring.gsub(text, "<%s*" .. tag .. "%f[^%w_].->", function(t) i = i + 1 if i > endcount then return "" else return nil end end) -- "end" here terminates the anonymous replacement function(t) passed to gsub elseif endcount > startcount then -- more </tag> than <tag>: remove the first few </tag>s text = mw.ustring.gsub(text, "<%s*/" .. tag .. "%f[^%w_].->", "", endcount - startcount) end return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted local gotopt local pageoptstr -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then -- We have page or [[page]] or [[page|text]], possibly followed by |opt1|opt2... local pn pn, gotopt, pageoptstr = mw.ustring.match(pagename, "^%s*(%[%b[]%])%s*(|?)(.*)") if pn then pagename = mw.ustring.match(pn, "%[%[([^|%]]*)") -- turn [[page|text]] into page, discarding text else -- we have page or page|opt... pagename, gotopt, pageoptstr = mw.ustring.match(pagename, "%s*([^|]*[^|%s])%s*(|?)(.*)") end if pagename and pagename ~= "" then local pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section and section ~= "" then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end local keepRefs if options.keepRefs == '1' then keepRefs = true else keepRefs = false end text = cleanupText(text, true, keepRefs) local pageopts = {} -- pageopts (even if value is "") have priority over global options for k, v in pairs(options) do pageopts[k] = v end if gotopt and gotopt ~= "" then for _, t in pairs(mw.text.split(pageoptstr, "|")) do local k, v = mw.ustring.match(t, "%s*([^=]-)%s*=(.-)%s*$") pageopts[k] = v end pageopts.paraflags = numberflags(pageopts["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} pageopts.fileflags = numberflags(pageopts["files"] or "") -- parse file numbers if pageopts.more and pageopts.more == "" then pageopts.more = "Read more..." end -- more= is short for this default text end local filetext filetext, text = parse(text, pageopts) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "()'''(.-'*)'''", function(a, b) if a < 100 and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return "'''[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]]''' else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- remove '''bold text''' if requested if pageopts.nobold then text = mw.ustring.gsub(text, "'''", "") end text = filetext .. text -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27") -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2") -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27") until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", "") -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", "") -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", "") -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. -- Ensure div tags match text = fixtags(text, "div") if pageopts.more then text = text .. " '''[[" .. pagename .. "|" .. pageopts.more .. "]]'''" end -- wikilink to article for more info if pageopts.list and not pageopts.showall then -- add a collapsed list of pages which might appear local listtext = pageopts.list if listtext == "" then listtext = "Other articles" end text = text .. "{{collapse top|title={{resize|85%|" ..listtext .. "}}|bg=fff}}{{hlist" for _, p in pairs(pagenames) do if mw.ustring.match(p, "%S") then text = text .. "|[[" .. mw.text.trim(p) .. "]]" end end text = text .. "}}\n{{collapse bottom}}" end return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = "" if options.showall then local separator = "" for _, p in pairs(pagenames) do local t = main({ p }, options) if t ~= "" then text = text .. separator .. t separator = options.showall if separator == "" then separator = "{{clear}}{{hr}}" end end end else text = main(pagenames, options) end if text == "" and brokenCategory and brokenCategory ~= "" and mw.title.getCurrentTitle().isContentPage then return "[[Category:" .. brokenCategory .. "]]" else return frame:preprocess(text) end end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, leadOnly, keepRefs) return cleanupText(text, leadOnly, keepRefs) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p efvjqblkskl9ciphr8rj613q6uimmws 797324 797323 2020-04-08T01:56:14Z en>Sophivorus 0 Update to latest sandbox version 797324 Scribunto text/plain -- Get localization data local d = require("Module:Excerpt/l10n") local p = {} local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- Helper function to test for falsy values local function falsy( value ) if not value or value == "" or value == "0" or value == "false" or value == "no" then return true end return false end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post, init) local match = {} for i = 1, #list do match = { mw.ustring.match(text, pre .. list[i] .. post, init) } if match[1] then return unpack(match) end end return nil end -- Help gsub to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchany(t, "^{{%s*", d.unwantedInlineTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noref = mw.ustring.gsub(noref, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local target = title.redirectTarget if target then title = target end return title:getContent(), title.prefixedText end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", d.fileNamespaces, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc, rtitle = getContent(page) -- get file description and title after following any redirect if desc and desc ~= "" then -- found description on local wiki if mw.ustring.match(desc, "[Nn]on%-free") then return false end desc = mw.ustring.gsub(desc, "%b{}", striptemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess elseif not rtitle then return false else -- try commons desc = "{{" .. rtitle .. "}}" end frame = frame or mw.getCurrentFrame() desc = frame:preprocess(desc) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", d.fileNamespaces, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end return caption -- No terminator found: return entire caption end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = matchany(text, "|%s*", d.captionParams, "%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position) if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", d.fileNamespaces, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", d.fileNamespaces, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end local imageArgGroups = { {"thumb", "thumbnail", "frame", "framed", "frameless"}, {"right", "left", "center", "none"}, {"baseline", "middle", "sub", "super", "text-top", "text-bottom", "top", "bottom"} } local function modifyImage(image, fileargs) if fileargs then for _, filearg in pairs(mw.text.split(fileargs, "|")) do -- handle fileargs=left|border etc. local fa = mw.ustring.gsub(filearg, "=.*", "") -- "upright=0.75" → "upright" local group = {fa} -- group of "border" is ["border"]... for _, g in pairs(imageArgGroups) do for _, a in pairs(g) do if fa == a then group = g end -- ...but group of "left" is ["right", "left", "center", "none"] end end for _, a in pairs(group) do image = mw.ustring.gsub(image, "|%s*" .. a .. "%f[%A]%s*=[^|%]]*", "") -- remove "|upright=0.75" etc. image = mw.ustring.gsub(image, "|%s*" .. a .. "%s*([|%]])", "%1") -- replace "|left|" by "|" etc. end image = mw.ustring.gsub(image, "([|%]])", "|" .. filearg .. "%1", 1) -- replace "|" by "|left|" etc. end end return image end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space -- Add named files local f = options.files if f and mw.ustring.match(f, "[^%d%s%-,]") then -- filename rather than number list f = mw.ustring.gsub(f, "^%s*File%s*:%s*", "", 1) f = mw.ustring.gsub(f, "^%s*Image%s*:%s*", "", 1) f = "[[File:" .. f .. "]]" f = modifyImage(f, "thumb") f = modifyImage(f, fileargs) if checkimage(f) then filetext = filetext .. f .. "\n" end end repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} or {| Table |} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif matchany(token, "{{%s*", d.wantedBlockTemplates, "%s*%f[|}]") then t = t .. token -- keep wanted block templates elseif not falsy(options.keepTables) and mw.ustring.sub(token, 1, 2) == '{|' then t = t .. token -- keep tables elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = modifyImage(image, "thumb") image = modifyImage(image, fileargs) filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original image = modifyImage(image, fileargs) filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$") -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return filetext, text end local function cleanupText(text, keepSubsections, keepRefs) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if falsy(keepSubsections) then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if the lead is empty end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits if mw.ustring.find(text, "[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]") then -- avoid expensive search if possible text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text between onlyinclude sections text = mw.ustring.gsub(text, "^.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text before first onlyinclude section text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.*", "") -- remove text after last onlyinclude section end if falsy(keepRefs) then text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references end text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Remove unmatched <tag> or </tag> tags local function fixtags(text, tag) local startcount = 0 for i in mw.ustring.gmatch(text, "<%s*" .. tag .. "%f[^%w_].->") do startcount = startcount + 1 end local endcount = 0 for i in mw.ustring.gmatch(text, "<%s*/" .. tag .. "%f[^%w_].->") do endcount = endcount + 1 end if startcount > endcount then -- more <tag> than </tag>: remove the last few <tag>s local i = 0 text = mw.ustring.gsub(text, "<%s*" .. tag .. "%f[^%w_].->", function(t) i = i + 1 if i > endcount then return "" else return nil end end) -- "end" here terminates the anonymous replacement function(t) passed to gsub elseif endcount > startcount then -- more </tag> than <tag>: remove the first few </tag>s text = mw.ustring.gsub(text, "<%s*/" .. tag .. "%f[^%w_].->", "", endcount - startcount) end return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted local gotopt local pageoptstr local section -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then -- We have page or [[page]] or [[page|text]], possibly followed by |opt1|opt2... local pn pn, gotopt, pageoptstr = mw.ustring.match(pagename, "^%s*(%[%b[]%])%s*(|?)(.*)") if pn then pagename = mw.ustring.match(pn, "%[%[([^|%]]*)") -- turn [[page|text]] into page, discarding text else -- we have page or page|opt... pagename, gotopt, pageoptstr = mw.ustring.match(pagename, "%s*([^|]*[^|%s])%s*(|?)(.*)") end if pagename and pagename ~= "" then local pn pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section and section ~= "" then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end local keepRefs = options.keepRefs local keepSubsections = options.keepSubsections text = cleanupText(text, keepSubsections, keepRefs) local pageopts = {} -- pageopts (even if value is "") have priority over global options for k, v in pairs(options) do pageopts[k] = v end if gotopt and gotopt ~= "" then for _, t in pairs(mw.text.split(pageoptstr, "|")) do local k, v = mw.ustring.match(t, "%s*([^=]-)%s*=(.-)%s*$") pageopts[k] = v end pageopts.paraflags = numberflags(pageopts["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} pageopts.fileflags = numberflags(pageopts["files"] or "") -- parse file numbers if pageopts.more and pageopts.more == "" then pageopts.more = "Read more..." end -- more= is short for this default text end local filetext filetext, text = parse(text, pageopts) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "()'''(.-'*)'''", function(a, b) if a < 100 and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return "'''[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]]''' else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- remove '''bold text''' if requested if not falsy(pageopts.nobold) then text = mw.ustring.gsub(text, "'''", "") end text = filetext .. text -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27") -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2") -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27") until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", "") -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", "") -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", "") -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. -- Ensure div tags match text = fixtags(text, "div") if pageopts.more then text = text .. " '''[[" .. pagename .. "|" .. pageopts.more .. "]]'''" end -- wikilink to article for more info if pageopts.list and not pageopts.showall then -- add a collapsed list of pages which might appear local listtext = pageopts.list if listtext == "" then listtext = "Other articles" end text = text .. "{{collapse top|title={{resize|85%|" ..listtext .. "}}|bg=fff}}{{hlist" for _, p in pairs(pagenames) do if mw.ustring.match(p, "%S") then text = text .. "|[[" .. mw.text.trim(p) .. "]]" end end text = text .. "}}\n{{collapse bottom}}" end return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = "" if options.showall then local separator = "" for _, p in pairs(pagenames) do local t = main({ p }, options) if t ~= "" then text = text .. separator .. t separator = options.showall if separator == "" then separator = "{{clear}}{{hr}}" end end end else text = main(pagenames, options) end if text == "" and d.brokenCategory and d.brokenCategory ~= "" and mw.title.getCurrentTitle().isContentPage then return "[[Category:" .. d.brokenCategory .. "]]" else return frame:preprocess(text) end end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, keepSubsections, keepRefs) return cleanupText(text, keepSubsections, keepRefs) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p pafcufkpebw9iyu6yvvajw88favptb3 797325 797324 2020-04-23T03:55:02Z en>JJMC89 0 Changed protection level for "[[Module:Excerpt]]": [[WP:High-risk templates|Highly visible module]], matching [[Template:Excerpt]] ([Edit=Require template editor access] (indefinite) [Move=Require template editor access] (indefinite)) 797324 Scribunto text/plain -- Get localization data local d = require("Module:Excerpt/l10n") local p = {} local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- Helper function to test for falsy values local function falsy( value ) if not value or value == "" or value == "0" or value == "false" or value == "no" then return true end return false end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post, init) local match = {} for i = 1, #list do match = { mw.ustring.match(text, pre .. list[i] .. post, init) } if match[1] then return unpack(match) end end return nil end -- Help gsub to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchany(t, "^{{%s*", d.unwantedInlineTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noref = mw.ustring.gsub(noref, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local target = title.redirectTarget if target then title = target end return title:getContent(), title.prefixedText end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", d.fileNamespaces, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc, rtitle = getContent(page) -- get file description and title after following any redirect if desc and desc ~= "" then -- found description on local wiki if mw.ustring.match(desc, "[Nn]on%-free") then return false end desc = mw.ustring.gsub(desc, "%b{}", striptemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess elseif not rtitle then return false else -- try commons desc = "{{" .. rtitle .. "}}" end frame = frame or mw.getCurrentFrame() desc = frame:preprocess(desc) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", d.fileNamespaces, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end return caption -- No terminator found: return entire caption end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = matchany(text, "|%s*", d.captionParams, "%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position) if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", d.fileNamespaces, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", d.fileNamespaces, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end local imageArgGroups = { {"thumb", "thumbnail", "frame", "framed", "frameless"}, {"right", "left", "center", "none"}, {"baseline", "middle", "sub", "super", "text-top", "text-bottom", "top", "bottom"} } local function modifyImage(image, fileargs) if fileargs then for _, filearg in pairs(mw.text.split(fileargs, "|")) do -- handle fileargs=left|border etc. local fa = mw.ustring.gsub(filearg, "=.*", "") -- "upright=0.75" → "upright" local group = {fa} -- group of "border" is ["border"]... for _, g in pairs(imageArgGroups) do for _, a in pairs(g) do if fa == a then group = g end -- ...but group of "left" is ["right", "left", "center", "none"] end end for _, a in pairs(group) do image = mw.ustring.gsub(image, "|%s*" .. a .. "%f[%A]%s*=[^|%]]*", "") -- remove "|upright=0.75" etc. image = mw.ustring.gsub(image, "|%s*" .. a .. "%s*([|%]])", "%1") -- replace "|left|" by "|" etc. end image = mw.ustring.gsub(image, "([|%]])", "|" .. filearg .. "%1", 1) -- replace "|" by "|left|" etc. end end return image end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space -- Add named files local f = options.files if f and mw.ustring.match(f, "[^%d%s%-,]") then -- filename rather than number list f = mw.ustring.gsub(f, "^%s*File%s*:%s*", "", 1) f = mw.ustring.gsub(f, "^%s*Image%s*:%s*", "", 1) f = "[[File:" .. f .. "]]" f = modifyImage(f, "thumb") f = modifyImage(f, fileargs) if checkimage(f) then filetext = filetext .. f .. "\n" end end repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} or {| Table |} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif matchany(token, "{{%s*", d.wantedBlockTemplates, "%s*%f[|}]") then t = t .. token -- keep wanted block templates elseif not falsy(options.keepTables) and mw.ustring.sub(token, 1, 2) == '{|' then t = t .. token -- keep tables elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = modifyImage(image, "thumb") image = modifyImage(image, fileargs) filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original image = modifyImage(image, fileargs) filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$") -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return filetext, text end local function cleanupText(text, keepSubsections, keepRefs) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if falsy(keepSubsections) then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if the lead is empty end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits if mw.ustring.find(text, "[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]") then -- avoid expensive search if possible text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text between onlyinclude sections text = mw.ustring.gsub(text, "^.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text before first onlyinclude section text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.*", "") -- remove text after last onlyinclude section end if falsy(keepRefs) then text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references end text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Remove unmatched <tag> or </tag> tags local function fixtags(text, tag) local startcount = 0 for i in mw.ustring.gmatch(text, "<%s*" .. tag .. "%f[^%w_].->") do startcount = startcount + 1 end local endcount = 0 for i in mw.ustring.gmatch(text, "<%s*/" .. tag .. "%f[^%w_].->") do endcount = endcount + 1 end if startcount > endcount then -- more <tag> than </tag>: remove the last few <tag>s local i = 0 text = mw.ustring.gsub(text, "<%s*" .. tag .. "%f[^%w_].->", function(t) i = i + 1 if i > endcount then return "" else return nil end end) -- "end" here terminates the anonymous replacement function(t) passed to gsub elseif endcount > startcount then -- more </tag> than <tag>: remove the first few </tag>s text = mw.ustring.gsub(text, "<%s*/" .. tag .. "%f[^%w_].->", "", endcount - startcount) end return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted local gotopt local pageoptstr local section -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then -- We have page or [[page]] or [[page|text]], possibly followed by |opt1|opt2... local pn pn, gotopt, pageoptstr = mw.ustring.match(pagename, "^%s*(%[%b[]%])%s*(|?)(.*)") if pn then pagename = mw.ustring.match(pn, "%[%[([^|%]]*)") -- turn [[page|text]] into page, discarding text else -- we have page or page|opt... pagename, gotopt, pageoptstr = mw.ustring.match(pagename, "%s*([^|]*[^|%s])%s*(|?)(.*)") end if pagename and pagename ~= "" then local pn pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section and section ~= "" then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end local keepRefs = options.keepRefs local keepSubsections = options.keepSubsections text = cleanupText(text, keepSubsections, keepRefs) local pageopts = {} -- pageopts (even if value is "") have priority over global options for k, v in pairs(options) do pageopts[k] = v end if gotopt and gotopt ~= "" then for _, t in pairs(mw.text.split(pageoptstr, "|")) do local k, v = mw.ustring.match(t, "%s*([^=]-)%s*=(.-)%s*$") pageopts[k] = v end pageopts.paraflags = numberflags(pageopts["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} pageopts.fileflags = numberflags(pageopts["files"] or "") -- parse file numbers if pageopts.more and pageopts.more == "" then pageopts.more = "Read more..." end -- more= is short for this default text end local filetext filetext, text = parse(text, pageopts) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "()'''(.-'*)'''", function(a, b) if a < 100 and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return "'''[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]]''' else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- remove '''bold text''' if requested if not falsy(pageopts.nobold) then text = mw.ustring.gsub(text, "'''", "") end text = filetext .. text -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27") -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2") -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27") until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", "") -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", "") -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", "") -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. -- Ensure div tags match text = fixtags(text, "div") if pageopts.more then text = text .. " '''[[" .. pagename .. "|" .. pageopts.more .. "]]'''" end -- wikilink to article for more info if pageopts.list and not pageopts.showall then -- add a collapsed list of pages which might appear local listtext = pageopts.list if listtext == "" then listtext = "Other articles" end text = text .. "{{collapse top|title={{resize|85%|" ..listtext .. "}}|bg=fff}}{{hlist" for _, p in pairs(pagenames) do if mw.ustring.match(p, "%S") then text = text .. "|[[" .. mw.text.trim(p) .. "]]" end end text = text .. "}}\n{{collapse bottom}}" end return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = "" if options.showall then local separator = "" for _, p in pairs(pagenames) do local t = main({ p }, options) if t ~= "" then text = text .. separator .. t separator = options.showall if separator == "" then separator = "{{clear}}{{hr}}" end end end else text = main(pagenames, options) end if text == "" and d.brokenCategory and d.brokenCategory ~= "" and mw.title.getCurrentTitle().isContentPage then return "[[Category:" .. d.brokenCategory .. "]]" else return frame:preprocess(text) end end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, keepSubsections, keepRefs) return cleanupText(text, keepSubsections, keepRefs) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p pafcufkpebw9iyu6yvvajw88favptb3 797326 797325 2020-04-23T18:08:04Z en>Ahecht 0 Implement {{[[Template:Excerpt|Excerpt]]}} with new excerpt() function. Allows articles to reduce [[WP:PEIS]] by calling module directly. 797326 Scribunto text/plain -- Get localization data local d = require("Module:Excerpt/l10n") local p = {} local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- Helper function to test for falsy values local function falsy( value ) if not value or value == "" or value == "0" or value == "false" or value == "no" then return true end return false end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post, init) local match = {} for i = 1, #list do match = { mw.ustring.match(text, pre .. list[i] .. post, init) } if match[1] then return unpack(match) end end return nil end -- Help gsub to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchany(t, "^{{%s*", d.unwantedInlineTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noref = mw.ustring.gsub(noref, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local target = title.redirectTarget if target then title = target end return title:getContent(), title.prefixedText end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", d.fileNamespaces, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc, rtitle = getContent(page) -- get file description and title after following any redirect if desc and desc ~= "" then -- found description on local wiki if mw.ustring.match(desc, "[Nn]on%-free") then return false end desc = mw.ustring.gsub(desc, "%b{}", striptemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess elseif not rtitle then return false else -- try commons desc = "{{" .. rtitle .. "}}" end frame = frame or mw.getCurrentFrame() desc = frame:preprocess(desc) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", d.fileNamespaces, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end return caption -- No terminator found: return entire caption end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = matchany(text, "|%s*", d.captionParams, "%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position) if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", d.fileNamespaces, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", d.fileNamespaces, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end local imageArgGroups = { {"thumb", "thumbnail", "frame", "framed", "frameless"}, {"right", "left", "center", "none"}, {"baseline", "middle", "sub", "super", "text-top", "text-bottom", "top", "bottom"} } local function modifyImage(image, fileargs) if fileargs then for _, filearg in pairs(mw.text.split(fileargs, "|")) do -- handle fileargs=left|border etc. local fa = mw.ustring.gsub(filearg, "=.*", "") -- "upright=0.75" → "upright" local group = {fa} -- group of "border" is ["border"]... for _, g in pairs(imageArgGroups) do for _, a in pairs(g) do if fa == a then group = g end -- ...but group of "left" is ["right", "left", "center", "none"] end end for _, a in pairs(group) do image = mw.ustring.gsub(image, "|%s*" .. a .. "%f[%A]%s*=[^|%]]*", "") -- remove "|upright=0.75" etc. image = mw.ustring.gsub(image, "|%s*" .. a .. "%s*([|%]])", "%1") -- replace "|left|" by "|" etc. end image = mw.ustring.gsub(image, "([|%]])", "|" .. filearg .. "%1", 1) -- replace "|" by "|left|" etc. end end return image end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space -- Add named files local f = options.files if f and mw.ustring.match(f, "[^%d%s%-,]") then -- filename rather than number list f = mw.ustring.gsub(f, "^%s*File%s*:%s*", "", 1) f = mw.ustring.gsub(f, "^%s*Image%s*:%s*", "", 1) f = "[[File:" .. f .. "]]" f = modifyImage(f, "thumb") f = modifyImage(f, fileargs) if checkimage(f) then filetext = filetext .. f .. "\n" end end repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} or {| Table |} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif matchany(token, "{{%s*", d.wantedBlockTemplates, "%s*%f[|}]") then t = t .. token -- keep wanted block templates elseif not falsy(options.keepTables) and mw.ustring.sub(token, 1, 2) == '{|' then t = t .. token -- keep tables elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = modifyImage(image, "thumb") image = modifyImage(image, fileargs) filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original image = modifyImage(image, fileargs) filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$") -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return filetext, text end local function cleanupText(text, keepSubsections, keepRefs) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if falsy(keepSubsections) then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if the lead is empty end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits if mw.ustring.find(text, "[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]") then -- avoid expensive search if possible text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text between onlyinclude sections text = mw.ustring.gsub(text, "^.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text before first onlyinclude section text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.*", "") -- remove text after last onlyinclude section end if falsy(keepRefs) then text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references end text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Remove unmatched <tag> or </tag> tags local function fixtags(text, tag) local startcount = 0 for i in mw.ustring.gmatch(text, "<%s*" .. tag .. "%f[^%w_].->") do startcount = startcount + 1 end local endcount = 0 for i in mw.ustring.gmatch(text, "<%s*/" .. tag .. "%f[^%w_].->") do endcount = endcount + 1 end if startcount > endcount then -- more <tag> than </tag>: remove the last few <tag>s local i = 0 text = mw.ustring.gsub(text, "<%s*" .. tag .. "%f[^%w_].->", function(t) i = i + 1 if i > endcount then return "" else return nil end end) -- "end" here terminates the anonymous replacement function(t) passed to gsub elseif endcount > startcount then -- more </tag> than <tag>: remove the first few </tag>s text = mw.ustring.gsub(text, "<%s*/" .. tag .. "%f[^%w_].->", "", endcount - startcount) end return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted local gotopt local pageoptstr local section -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then -- We have page or [[page]] or [[page|text]], possibly followed by |opt1|opt2... local pn pn, gotopt, pageoptstr = mw.ustring.match(pagename, "^%s*(%[%b[]%])%s*(|?)(.*)") if pn then pagename = mw.ustring.match(pn, "%[%[([^|%]]*)") -- turn [[page|text]] into page, discarding text else -- we have page or page|opt... pagename, gotopt, pageoptstr = mw.ustring.match(pagename, "%s*([^|]*[^|%s])%s*(|?)(.*)") end if pagename and pagename ~= "" then local pn pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section and section ~= "" then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end local keepRefs = options.keepRefs local keepSubsections = options.keepSubsections text = cleanupText(text, keepSubsections, keepRefs) local pageopts = {} -- pageopts (even if value is "") have priority over global options for k, v in pairs(options) do pageopts[k] = v end if gotopt and gotopt ~= "" then for _, t in pairs(mw.text.split(pageoptstr, "|")) do local k, v = mw.ustring.match(t, "%s*([^=]-)%s*=(.-)%s*$") pageopts[k] = v end pageopts.paraflags = numberflags(pageopts["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} pageopts.fileflags = numberflags(pageopts["files"] or "") -- parse file numbers if pageopts.more and pageopts.more == "" then pageopts.more = "Read more..." end -- more= is short for this default text end local filetext filetext, text = parse(text, pageopts) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "()'''(.-'*)'''", function(a, b) if a < 100 and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return "'''[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]]''' else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- remove '''bold text''' if requested if not falsy(pageopts.nobold) then text = mw.ustring.gsub(text, "'''", "") end text = filetext .. text -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27") -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2") -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27") until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", "") -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", "") -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", "") -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. -- Ensure div tags match text = fixtags(text, "div") if pageopts.more then text = text .. " '''[[" .. pagename .. "|" .. pageopts.more .. "]]'''" end -- wikilink to article for more info if pageopts.list and not pageopts.showall then -- add a collapsed list of pages which might appear local listtext = pageopts.list if listtext == "" then listtext = "Other articles" end text = text .. "{{collapse top|title={{resize|85%|" ..listtext .. "}}|bg=fff}}{{hlist" for _, p in pairs(pagenames) do if mw.ustring.match(p, "%S") then text = text .. "|[[" .. mw.text.trim(p) .. "]]" end end text = text .. "}}\n{{collapse bottom}}" end return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = "" if options.showall then local separator = "" for _, p in pairs(pagenames) do local t = main({ p }, options) if t ~= "" then text = text .. separator .. t separator = options.showall if separator == "" then separator = "{{clear}}{{hr}}" end end end else text = main(pagenames, options) end if text == "" and d.brokenCategory and d.brokenCategory ~= "" and mw.title.getCurrentTitle().isContentPage then return "[[Category:" .. d.brokenCategory .. "]]" else return frame:preprocess(text) end end local function is(v) return (v or '') ~= '' end local function excerpt(frame) -- Replicate {{Excerpt}} entirely in Lua for reduced Post-expand include size local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template local tag = is(args.tag) and args.tag or 'div' local article = is(args.article) and args.article or args[1] or '{{{1}}}' local section = is(args.section) and args.section or args[2] local output = {} output[1] = frame:extensionTag{ name = 'templatestyles', args = {src='Excerpt/styles.css'} } output[2] = '<' .. tag .. ' class="excerpt-block">' output[3] = is(args.indicator) and ('<' .. tag .. ' class="excerpt-indicator">') or '' if is(args.nohat) then output[4] = '' else local hatnote = {} hatnote[1] = 'This' .. (is(args.indicator) and '' or ' section') .. ' is an excerpt from ' hatnote[2] = '[[' hatnote[3] = article .. (is(section) and ('#' .. frame:callParserFunction( 'urlencode', section, 'WIKI' )) or '') hatnote[4] = '|' hatnote[5] = article .. (is(section) and (frame:callParserFunction( '#tag:nowiki', ' § ' ) .. section) or '') hatnote[6] = ']]' hatnote[7] = "''" .. '<span class="mw-editsection-like plainlinks"><span>[ </span>[' local title = mw.title.new(article) or mw.title.getCurrentTitle() hatnote[8] = title:fullUrl('action=edit') .. ' edit' hatnote[9] = ']<span> ]</span></span>' .. "''" output[4] = require('Module:Hatnote')._hatnote(table.concat(hatnote), {selfref=true}) end output[5] = '<' .. tag .. ' class="excerpt">\n' if article ~= '{{{1}}}' then if is(args.fragment) then output[6] = frame:callParserFunction( '#lst', article, args.fragment) or '' else local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args.paragraphs or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args.files or "1") -- parse file numbers options.nobold=1 options.keepTables = is(args.tables) and args.tables or 1 options.keepRefs = is(args.references) and args.references or 1 options.keepSubsections = is(args.subsections) and args.subsections or "" if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local pagenames = { (article .. '#' .. (section or '')) } local text = main(pagenames, options) if text == "" and d.brokenCategory and d.brokenCategory ~= "" and mw.title.getCurrentTitle().isContentPage then output[6] = "[[Category:" .. d.brokenCategory .. "]]" else output[6] = frame:preprocess(text) end end else output[6] = err("No article provided") end output[7] = '</' .. tag .. '>' output[8] = is(args.indicator) and ('</' .. tag .. '>') or '' output[9] = '</' .. tag .. '>' output[10] = mw.title.getCurrentTitle().isContentPage and '[[Category:Articles with excerpts]]' or '' return table.concat(output) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter function p.excerpt(frame) return excerpt(frame) end -- {{Excerpt}} transcludes part of an article into another article -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, keepSubsections, keepRefs) return cleanupText(text, keepSubsections, keepRefs) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p ned6juzorez6fkpissp3pxugxq8yxc3 797327 797326 2020-04-23T20:02:55Z en>Ahecht 0 better error handling 797327 Scribunto text/plain -- Get localization data local d = require("Module:Excerpt/l10n") local p = {} local errors -- Return blank text, or an error message if requested local function err(text) if errors then error(text, 2) end return "" end -- Helper function to test for falsy values local function falsy( value ) if not value or value == "" or value == "0" or value == "false" or value == "no" then return true end return false end -- In text, match pre..list[1]..post or pre..list[2]..post or ... local function matchany(text, pre, list, post, init) local match = {} for i = 1, #list do match = { mw.ustring.match(text, pre .. list[i] .. post, init) } if match[1] then return unpack(match) end end return nil end -- Help gsub to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function striptemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchany(t, "^{{%s*", d.unwantedInlineTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noref = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noref = mw.ustring.gsub(noref, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noref = mw.ustring.sub(noref, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noref, 3), "%b{}", striptemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noref = mw.ustring.gsub(noref, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noref = mw.ustring.gsub(noref, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noref ~= t then return noref end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local target = title.redirectTarget if target then title = target end return title:getContent(), title.prefixedText end -- Check image for suitability local function checkimage(image) local page = matchany(image, "", d.fileNamespaces, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchany(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local desc, rtitle = getContent(page) -- get file description and title after following any redirect if desc and desc ~= "" then -- found description on local wiki if mw.ustring.match(desc, "[Nn]on%-free") then return false end desc = mw.ustring.gsub(desc, "%b{}", striptemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess elseif not rtitle then return false else -- try commons desc = "{{" .. rtitle .. "}}" end frame = frame or mw.getCurrentFrame() desc = frame:preprocess(desc) return ( desc and desc ~= "" and not mw.ustring.match(desc, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseimage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchany(text, startre .. "%[%[%s*", d.fileNamespaces, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parsecaption(caption) if not caption then return nil end local len = mw.ustring.len(caption) local pos = 1 while pos <= len do local linkstart, linkend = mw.ustring.find(caption, "%b[]", pos) linkstart = linkstart or len + 1 -- avoid comparison with nil when no link local templatestart, templateend = mw.ustring.find(caption, "%b{}", pos) templatestart = templatestart or len + 1 -- avoid comparison with nil when no template local argend = mw.ustring.find(caption, "[|}]", pos) or len + 1 if linkstart < templatestart and linkstart < argend then pos = linkend + 1 -- skip wikilink elseif templatestart < argend then pos = templateend + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argend - 1) end end return caption -- No terminator found: return entire caption end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argimage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local capture_from = 1 while capture_from < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", capture_from) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgname = mw.ustring.lower(argname) if mw.ustring.find(lcArgname, "caption") or mw.ustring.find(lcArgname, "size") or mw.ustring.find(lcArgname, "upright") then image = nil end end if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", capture_from) if image then hasImages = true images[position] = image capture_from = position else capture_from = mw.ustring.len(text) end end capture_from = 1 while capture_from < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", capture_from) if image then hasImages = true if not images[position] then images[position] = image end capture_from = position else capture_from = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} capture_from = 1 while capture_from < mw.ustring.len(text) do local position, caption = matchany(text, "|%s*", d.captionParams, "%s*=%s*()([^\n]+)", capture_from) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position) if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parsecaption(caption) end end end capture_from = position else capture_from = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookfrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local len = mw.ustring.len(altText) local aftertext = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookfrom) or len+1, mw.ustring.match(altText, "()|", lookfrom) or len+1) altText = mw.ustring.sub(altText, 1, aftertext-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseimage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchany(image, "^", d.fileNamespaces, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImagemap(imagemap) local image = matchany(imagemap, "[>\n]%s*", d.fileNamespaces, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberflags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end local imageArgGroups = { {"thumb", "thumbnail", "frame", "framed", "frameless"}, {"right", "left", "center", "none"}, {"baseline", "middle", "sub", "super", "text-top", "text-bottom", "top", "bottom"} } local function modifyImage(image, fileargs) if fileargs then for _, filearg in pairs(mw.text.split(fileargs, "|")) do -- handle fileargs=left|border etc. local fa = mw.ustring.gsub(filearg, "=.*", "") -- "upright=0.75" → "upright" local group = {fa} -- group of "border" is ["border"]... for _, g in pairs(imageArgGroups) do for _, a in pairs(g) do if fa == a then group = g end -- ...but group of "left" is ["right", "left", "center", "none"] end end for _, a in pairs(group) do image = mw.ustring.gsub(image, "|%s*" .. a .. "%f[%A]%s*=[^|%]]*", "") -- remove "|upright=0.75" etc. image = mw.ustring.gsub(image, "|%s*" .. a .. "%s*([|%]])", "%1") -- replace "|left|" by "|" etc. end image = mw.ustring.gsub(image, "([|%]])", "|" .. filearg .. "%1", 1) -- replace "|" by "|left|" etc. end end return image end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allparas = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberflags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allparas = false end -- if any para specifically requested, don't keep all end end if filesOnly then allparas = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberflags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileargs = options.fileargs and mw.text.trim(options.fileargs) if fileargs == '' then fileargs = nil end local leadstart = nil -- have we found some text yet? local t = "" -- the stripped down output text local filetext = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space -- Add named files local f = options.files if f and mw.ustring.match(f, "[^%d%s%-,]") then -- filename rather than number list f = mw.ustring.gsub(f, "^%s*File%s*:%s*", "", 1) f = mw.ustring.gsub(f, "^%s*Image%s*:%s*", "", 1) f = "[[File:" .. f .. "]]" f = modifyImage(f, "thumb") f = modifyImage(f, fileargs) if checkimage(f) then filetext = filetext .. f .. "\n" end end repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} or {| Table |} if not leadstart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchany(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadstart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif matchany(token, "{{%s*", d.wantedBlockTemplates, "%s*%f[|}]") then t = t .. token -- keep wanted block templates elseif not falsy(options.keepTables) and mw.ustring.sub(token, 1, 2) == '{|' then t = t .. token -- keep tables elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argimage(token) or {} if not images then local image = parseimage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkimage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = modifyImage(image, "thumb") image = modifyImage(image, fileargs) filetext = filetext .. image end end end end else -- the next token in text is not a template token = parseimage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkimage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original image = modifyImage(image, fileargs) filetext = filetext .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterend = mw.ustring.len(text) + 1 local blankpos = mw.ustring.find(text, "\n%s*\n") or afterend -- position of next paragraph delimiter (or end of text) local endpos = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterend, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterend, blankpos) token = mw.ustring.sub(text, 1, endpos-1) if blankpos < afterend and blankpos == endpos then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankpos) end local isHatnote = not(leadstart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadstart = leadstart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allparas or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$") -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return filetext, text end local function cleanupText(text, keepSubsections, keepRefs) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments if falsy(keepSubsections) then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if the lead is empty end text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits if mw.ustring.find(text, "[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]") then -- avoid expensive search if possible text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text between onlyinclude sections text = mw.ustring.gsub(text, "^.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text before first onlyinclude section text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.*", "") -- remove text after last onlyinclude section end if falsy(keepRefs) then text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "%b{}", striptemplate) -- remove unwanted templates such as references end text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImagemap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getsection(text, section, mainonly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextsection if mainonly then nextsection = "\n==.*" -- Main part of section terminates at any level of header else nextsection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextsection, "") -- remove later sections with headings at this level or higher return content end -- Remove unmatched <tag> or </tag> tags local function fixtags(text, tag) local startcount = 0 for i in mw.ustring.gmatch(text, "<%s*" .. tag .. "%f[^%w_].->") do startcount = startcount + 1 end local endcount = 0 for i in mw.ustring.gmatch(text, "<%s*/" .. tag .. "%f[^%w_].->") do endcount = endcount + 1 end if startcount > endcount then -- more <tag> than </tag>: remove the last few <tag>s local i = 0 text = mw.ustring.gsub(text, "<%s*" .. tag .. "%f[^%w_].->", function(t) i = i + 1 if i > endcount then return "" else return nil end end) -- "end" here terminates the anonymous replacement function(t) passed to gsub elseif endcount > startcount then -- more </tag> than <tag>: remove the first few </tag>s text = mw.ustring.gsub(text, "<%s*/" .. tag .. "%f[^%w_].->", "", endcount - startcount) end return text end -- Main function returns a string value: text of the lead of a page local function main(pagenames, options) if not pagenames or #pagenames < 1 then return err("No page names given") end local pagename local text local pagecount = #pagenames local firstpage = pagenames[1] or "(nil)" -- save for error message, as it the name will be deleted local gotopt local pageoptstr local section -- read the page, or a random one if multiple pages were provided if pagecount > 1 then math.randomseed(os.time()) end while not text and pagecount > 0 do local pagenum = 1 if pagecount > 1 then pagenum = math.random(pagecount) end -- pick a random title pagename = pagenames[pagenum] if pagename and pagename ~= "" then -- We have page or [[page]] or [[page|text]], possibly followed by |opt1|opt2... local pn pn, gotopt, pageoptstr = mw.ustring.match(pagename, "^%s*(%[%b[]%])%s*(|?)(.*)") if pn then pagename = mw.ustring.match(pn, "%[%[([^|%]]*)") -- turn [[page|text]] into page, discarding text else -- we have page or page|opt... pagename, gotopt, pageoptstr = mw.ustring.match(pagename, "%s*([^|]*[^|%s])%s*(|?)(.*)") end if pagename and pagename ~= "" then local pn pn, section = mw.ustring.match(pagename, "(.-)#(.*)") pagename = pn or pagename text, normalisedPagename = getContent(pagename) if not normalisedPagename then return err("No title for page name " .. pagename) else pagename = normalisedPagename end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pagename, ".-#(.*)") -- parse redirect to Page#Section end if text and section and section ~= "" then text = getsection(text, section) end end end if not text then table.remove(pagenames, pagenum) end -- this one didn't work; try another pagecount = pagecount - 1 -- ensure that we exit the loop after at most #pagenames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstpage) end local keepRefs = options.keepRefs local keepSubsections = options.keepSubsections text = cleanupText(text, keepSubsections, keepRefs) local pageopts = {} -- pageopts (even if value is "") have priority over global options for k, v in pairs(options) do pageopts[k] = v end if gotopt and gotopt ~= "" then for _, t in pairs(mw.text.split(pageoptstr, "|")) do local k, v = mw.ustring.match(t, "%s*([^=]-)%s*=(.-)%s*$") pageopts[k] = v end pageopts.paraflags = numberflags(pageopts["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} pageopts.fileflags = numberflags(pageopts["files"] or "") -- parse file numbers if pageopts.more and pageopts.more == "" then pageopts.more = "Read more..." end -- more= is short for this default text end local filetext filetext, text = parse(text, pageopts) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pagename) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pagename) .. "'''", 1, true) -- plain search: special characters in pagename represent themselves if pos then local len = mw.ustring.len(pagename) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "()'''(.-'*)'''", function(a, b) if a < 100 and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return "'''[[" .. pagename .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pagename|Foo]]''' else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- remove '''bold text''' if requested if not falsy(pageopts.nobold) then text = mw.ustring.gsub(text, "'''", "") end text = filetext .. text -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27") -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2") -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27") until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", "") -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", "") -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", "") -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. -- Ensure div tags match text = fixtags(text, "div") if pageopts.more then text = text .. " '''[[" .. pagename .. "|" .. pageopts.more .. "]]'''" end -- wikilink to article for more info if pageopts.list and not pageopts.showall then -- add a collapsed list of pages which might appear local listtext = pageopts.list if listtext == "" then listtext = "Other articles" end text = text .. "{{collapse top|title={{resize|85%|" ..listtext .. "}}|bg=fff}}{{hlist" for _, p in pairs(pagenames) do if mw.ustring.match(p, "%S") then text = text .. "|[[" .. mw.text.trim(p) .. "]]" end end text = text .. "}}\n{{collapse bottom}}" end return text end -- Shared template invocation code for lead and random functions local function invoke(frame, func) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articlecount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articlecount < 1 and not (func == "selected" and args[func] and args[args[func]]) then return err("No articles provided") end local pagenames = {} if func == "lead" then pagenames = { args[1] } elseif func == "linked" or func == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getsection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if func == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pagenames, p) end end elseif func == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pagenames, p) end end elseif func == "selected" then local articlekey = args[func] if tonumber(articlekey) then -- normalise article number into the range 1..#args articlekey = articlekey % articlecount if articlekey == 0 then articlekey = articlecount end end pagenames = { args[articlekey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = "" if options.showall then local separator = "" for _, p in pairs(pagenames) do local t = main({ p }, options) if t ~= "" then text = text .. separator .. t separator = options.showall if separator == "" then separator = "{{clear}}{{hr}}" end end end else text = main(pagenames, options) end if text == "" and d.brokenCategory and d.brokenCategory ~= "" and mw.title.getCurrentTitle().isContentPage then return "[[Category:" .. d.brokenCategory .. "]]" else return frame:preprocess(text) end end local function is(v) return (v or '') ~= '' end local function excerpt(frame) -- Replicate {{Excerpt}} entirely in Lua for reduced Post-expand include size local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template local tag = is(args.tag) and args.tag or 'div' local article = is(args.article) and args.article or args[1] or '{{{1}}}' local section = is(args.section) and args.section or args[2] local output = {} output[1] = frame:extensionTag{ name = 'templatestyles', args = {src='Excerpt/styles.css'} } output[2] = '<' .. tag .. ' class="excerpt-block">' output[3] = is(args.indicator) and ('<' .. tag .. ' class="excerpt-indicator">') or '' if is(args.nohat) then output[4] = '' else local hatnote = {} hatnote[1] = 'This' .. (is(args.indicator) and '' or ' section') .. ' is an excerpt from ' hatnote[2] = '[[' hatnote[3] = article .. (is(section) and ('#' .. frame:callParserFunction( 'urlencode', section, 'WIKI' )) or '') hatnote[4] = '|' hatnote[5] = article .. (is(section) and (frame:callParserFunction( '#tag:nowiki', ' § ' ) .. section) or '') hatnote[6] = ']]' hatnote[7] = "''" .. '<span class="mw-editsection-like plainlinks"><span>[ </span>[' local title = mw.title.new(article) or mw.title.getCurrentTitle() hatnote[8] = title:fullUrl('action=edit') .. ' edit' hatnote[9] = ']<span> ]</span></span>' .. "''" output[4] = require('Module:Hatnote')._hatnote(table.concat(hatnote), {selfref=true}) or '' end output[5] = '<' .. tag .. ' class="excerpt">\n' if article ~= '{{{1}}}' then if is(args.fragment) then output[6] = frame:callParserFunction( '#lst', article, args.fragment) or err("Error transcluding text") else local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberflags(args.paragraphs or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberflags(args.files or "1") -- parse file numbers options.nobold=1 options.keepTables = is(args.tables) and args.tables or 1 options.keepRefs = is(args.references) and args.references or 1 options.keepSubsections = is(args.subsections) and args.subsections or "" if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local pagenames = { (article .. '#' .. (section or '')) } local text = main(pagenames, options) if text == "" and d.brokenCategory and d.brokenCategory ~= "" and mw.title.getCurrentTitle().isContentPage then output[6] = "[[Category:" .. d.brokenCategory .. "]]" else output[6] = frame:preprocess(text) or err("Error processing text") end end else output[6] = err("No article provided") end output[7] = '</' .. tag .. '>' output[8] = is(args.indicator) and ('</' .. tag .. '>') or '' output[9] = '</' .. tag .. '>' output[10] = mw.title.getCurrentTitle().isContentPage and '[[Category:Articles with excerpts]]' or '' return table.concat(output) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter function p.excerpt(frame) return excerpt(frame) end -- {{Excerpt}} transcludes part of an article into another article -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getsection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argimage(text) end function p.checkimage(image) return checkimage(image) end function p.parseimage(text, start) return parseimage(text, start) end function p.cleanupText(text, keepSubsections, keepRefs) return cleanupText(text, keepSubsections, keepRefs) end function p.main(pagenames, options) return main(pagenames, options) end function p.numberflags(str) return numberflags(str) end return p 9s80nrungsb0i5693mptniwfbchnvj2 797328 797327 2020-04-29T22:49:51Z en>Sophivorus 0 Update to latest sandbox version. Integrate #lst into the module. Change function and variable names to camelCase. Move localization submodule from /l10n to /i18n. Simplify the excerpt function. 797328 Scribunto text/plain -- Get localized data local d = require("Module:Excerpt/i18n") local p = {} -- Helper function to debug -- Returns blank text or an error message if requested local errors local function err(msg,a,b) local text = mw.ustring.format(d.error[msg] or msg or '',a,b) if errors then error(text, 2) end return "" end -- Helper function to test for truthy and falsy values local function is(value) if not value or value == "" or value == "0" or value == "false" or value == "no" then return false end return true end -- Helper function to match from a list regular expressions -- Like so: match pre..list[1]..post or pre..list[2]..post or ... local function matchAny(text, pre, list, post, init) local match = {} for i = 1, #list do match = { mw.ustring.match(text, pre .. list[i] .. post, init) } if match[1] then return unpack(match) end end return nil end -- Help gsub to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function stripTemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchAny(t, "^{{%s*", d.unwantedInlineTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noRef = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noRef = mw.ustring.gsub(noRef, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noRef = mw.ustring.sub(noRef, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noRef, 3), "%b{}", stripTemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noRef = mw.ustring.gsub(noRef, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noRef = mw.ustring.gsub(noRef, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noRef ~= t then return noRef end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local target = title.redirectTarget if target then title = target end return title:getContent(), title.prefixedText end -- Check image for suitability local function checkImage(image) local page = matchAny(image, "", d.fileNamespaces, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchAny(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local fileDescription, fileTitle = getContent(page) -- get file description and title after following any redirect if fileDescription and fileDescription ~= "" then -- found description on local wiki if mw.ustring.match(fileDescription, "[Nn]on%-free") then return false end fileDescription = mw.ustring.gsub(fileDescription, "%b{}", stripTemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess elseif not fileTitle then return false else -- try commons fileDescription = "{{" .. fileTitle .. "}}" end frame = frame or mw.getCurrentFrame() fileDescription = frame:preprocess(fileDescription) return ( fileDescription and fileDescription ~= "" and not mw.ustring.match(fileDescription, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseImage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchAny(text, startre .. "%[%[%s*", d.fileNamespaces, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parseCaption(caption) if not caption then return nil end local length = mw.ustring.len(caption) local position = 1 while position <= length do local linkStart, linkEnd = mw.ustring.find(caption, "%b[]", position) linkStart = linkStart or length + 1 -- avoid comparison with nil when no link local templateStart, templateEnd = mw.ustring.find(caption, "%b{}", position) templateStart = templateStart or length + 1 -- avoid comparison with nil when no template local argEnd = mw.ustring.find(caption, "[|}]", position) or length + 1 if linkStart < templateStart and linkStart < argEnd then position = linkEnd + 1 -- skip wikilink elseif templateStart < argEnd then position = templateEnd + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argEnd - 1) end end return caption -- No terminator found: return entire caption end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argImage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local captureFrom = 1 while captureFrom < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", captureFrom) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgName = mw.ustring.lower(argname) if mw.ustring.find(lcArgName, "caption") or mw.ustring.find(lcArgName, "size") or mw.ustring.find(lcArgName, "upright") then image = nil end end if image then hasImages = true images[position] = image captureFrom = position else captureFrom = mw.ustring.len(text) end end captureFrom = 1 while captureFrom < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", captureFrom) if image then hasImages = true images[position] = image captureFrom = position else captureFrom = mw.ustring.len(text) end end captureFrom = 1 while captureFrom < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", captureFrom) if image then hasImages = true if not images[position] then images[position] = image end captureFrom = position else captureFrom = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} captureFrom = 1 while captureFrom < mw.ustring.len(text) do local position, caption = matchAny(text, "|%s*", d.captionParams, "%s*=%s*()([^\n]+)", captureFrom) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position) if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parseCaption(caption) end end end captureFrom = position else captureFrom = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookFrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local length = mw.ustring.len(altText) local afterText = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookFrom) or length+1, mw.ustring.match(altText, "()|", lookFrom) or length+1) altText = mw.ustring.sub(altText, 1, afterText-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseImage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchAny(image, "^", d.fileNamespaces, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImageMap(imagemap) local image = matchAny(imagemap, "[>\n]%s*", d.fileNamespaces, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberFlags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end local imageArgGroups = { {"thumb", "thumbnail", "frame", "framed", "frameless"}, {"right", "left", "center", "none"}, {"baseline", "middle", "sub", "super", "text-top", "text-bottom", "top", "bottom"} } local function modifyImage(image, fileArgs) if fileArgs then for _, filearg in pairs(mw.text.split(fileArgs, "|")) do -- handle fileArgs=left|border etc. local fa = mw.ustring.gsub(filearg, "=.*", "") -- "upright=0.75" → "upright" local group = {fa} -- group of "border" is ["border"]... for _, g in pairs(imageArgGroups) do for _, a in pairs(g) do if fa == a then group = g end -- ...but group of "left" is ["right", "left", "center", "none"] end end for _, a in pairs(group) do image = mw.ustring.gsub(image, "|%s*" .. a .. "%f[%A]%s*=[^|%]]*", "") -- remove "|upright=0.75" etc. image = mw.ustring.gsub(image, "|%s*" .. a .. "%s*([|%]])", "%1") -- replace "|left|" by "|" etc. end image = mw.ustring.gsub(image, "([|%]])", "|" .. filearg .. "%1", 1) -- replace "|" by "|left|" etc. end end return image end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allParagraphs = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberFlags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allParagraphs = false end -- if any para specifically requested, don't keep all end end if filesOnly then allParagraphs = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberFlags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileArgs = options.fileargs and mw.text.trim(options.fileargs) if fileArgs == '' then fileArgs = nil end local leadStart = nil -- have we found some text yet? local t = "" -- the stripped down output text local fileText = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space -- Add named files local f = options.files if f and mw.ustring.match(f, "[^%d%s%-,]") then -- filename rather than number list f = mw.ustring.gsub(f, "^%s*File%s*:%s*", "", 1) f = mw.ustring.gsub(f, "^%s*Image%s*:%s*", "", 1) f = "[[File:" .. f .. "]]" f = modifyImage(f, "thumb") f = modifyImage(f, fileArgs) if checkImage(f) then fileText = fileText .. f .. "\n" end end repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} or {| Table |} if not leadStart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchAny(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadStart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif matchAny(token, "{{%s*", d.wantedBlockTemplates, "%s*%f[|}]") then t = t .. token -- keep wanted block templates elseif is(options.keepTables) and mw.ustring.sub(token, 1, 2) == '{|' then t = t .. token -- keep tables elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argImage(token) or {} if not images then local image = parseImage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkImage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = modifyImage(image, "thumb") image = modifyImage(image, fileArgs) fileText = fileText .. image end end end end else -- the next token in text is not a template token = parseImage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkImage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original image = modifyImage(image, fileArgs) fileText = fileText .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterEnd = mw.ustring.len(text) + 1 local blankPosition = mw.ustring.find(text, "\n%s*\n") or afterEnd -- position of next paragraph delimiter (or end of text) local endPosition = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterEnd, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterEnd, blankPosition) token = mw.ustring.sub(text, 1, endPosition-1) if blankPosition < afterEnd and blankPosition == endPosition then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankPosition) end local isHatnote = not(leadStart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadStart = leadStart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allParagraphs or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$") -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return fileText, text end local function cleanupText(text, options) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits if mw.ustring.find(text, "[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]") then -- avoid expensive search if possible text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text between onlyinclude sections text = mw.ustring.gsub(text, "^.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text before first onlyinclude section text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.*", "") -- remove text after last onlyinclude section end if not is(options.keepSubsections) then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if the lead is empty end if not is(options.keepRefs) then text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "%b{}", stripTemplate) -- remove unwanted templates such as references end text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImageMap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getSection(text, section, mainOnly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextSection if mainOnly then nextSection = "\n==.*" -- Main part of section terminates at any level of header else nextSection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextSection, "") -- remove later sections with headings at this level or higher return content end -- Remove unmatched <tag> or </tag> tags local function fixTags(text, tag) local startCount = 0 for i in mw.ustring.gmatch(text, "<%s*" .. tag .. "%f[^%w_].->") do startCount = startCount + 1 end local endCount = 0 for i in mw.ustring.gmatch(text, "<%s*/" .. tag .. "%f[^%w_].->") do endCount = endCount + 1 end if startCount > endCount then -- more <tag> than </tag>: remove the last few <tag>s local i = 0 text = mw.ustring.gsub(text, "<%s*" .. tag .. "%f[^%w_].->", function(t) i = i + 1 if i > endCount then return "" else return nil end end) -- "end" here terminates the anonymous replacement function(t) passed to gsub elseif endCount > startCount then -- more </tag> than <tag>: remove the first few </tag>s text = mw.ustring.gsub(text, "<%s*/" .. tag .. "%f[^%w_].->", "", endCount - startCount) end return text end -- Main function returns a string value: text of the lead of a page local function main(pageNames, options) if not pageNames or #pageNames < 1 then return err("No page names given") end local pageName local text local pageCount = #pageNames local firstPage = pageNames[1] or "(nil)" -- save for error message, as it the name will be deleted local gotOptions local pageOptionsString local section -- read the page, or a random one if multiple pages were provided if pageCount > 1 then math.randomseed(os.time()) end while not text and pageCount > 0 do local pageNumber = 1 if pageCount > 1 then pageNumber = math.random(pageCount) end -- pick a random title pageName = pageNames[pageNumber] if pageName and pageName ~= "" then -- We have page or [[page]] or [[page|text]], possibly followed by |opt1|opt2... local pn pn, gotOptions, pageOptionsString = mw.ustring.match(pageName, "^%s*(%[%b[]%])%s*(|?)(.*)") if pn then pageName = mw.ustring.match(pn, "%[%[([^|%]]*)") -- turn [[page|text]] into page, discarding text else -- we have page or page|opt... pageName, gotOptions, pageOptionsString = mw.ustring.match(pageName, "%s*([^|]*[^|%s])%s*(|?)(.*)") end if pageName and pageName ~= "" then local pn pn, section = mw.ustring.match(pageName, "(.-)#(.*)") pageName = pn or pageName text, normalisedPageName = getContent(pageName) if is(options.fragment) then local frame = mw.getCurrentFrame() text = frame:callParserFunction('#lst', normalisedPageName, options.fragment) end if not normalisedPageName then return err("No title for page name " .. pageName) else pageName = normalisedPageName end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pageName, ".-#(.*)") -- parse redirect to Page#Section end if text and section and section ~= "" then text = getSection(text, section) end end end if not text then table.remove(pageNames, pageNumber) end -- this one didn't work; try another pageCount = pageCount - 1 -- ensure that we exit the loop after at most #pageNames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstPage) end text = cleanupText(text, options) local pageOptions = {} -- pageOptions (even if value is "") have priority over global options for k, v in pairs(options) do pageOptions[k] = v end if gotOptions and gotOptions ~= "" then for _, t in pairs(mw.text.split(pageOptionsString, "|")) do local k, v = mw.ustring.match(t, "%s*([^=]-)%s*=(.-)%s*$") pageOptions[k] = v end pageOptions.paraflags = numberFlags(pageOptions["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} pageOptions.fileflags = numberFlags(pageOptions["files"] or "") -- parse file numbers if pageOptions.more and pageOptions.more == "" then pageOptions.more = "Read more..." end -- more= is short for this default text end local fileText fileText, text = parse(text, pageOptions) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pageName) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pageName) .. "'''", 1, true) -- plain search: special characters in pageName represent themselves if pos then local len = mw.ustring.len(pageName) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "()'''(.-'*)'''", function(a, b) if a < 100 and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return "'''[[" .. pageName .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pageName|Foo]]''' else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- remove '''bold text''' if requested if is(pageOptions.nobold) then text = mw.ustring.gsub(text, "'''", "") end text = fileText .. text -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27") -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2") -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27") until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", "") -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", "") -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", "") -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. -- Ensure div tags match text = fixTags(text, "div") if pageOptions.more then text = text .. " '''[[" .. pageName .. "|" .. pageOptions.more .. "]]'''" end -- wikilink to article for more info if pageOptions.list and not pageOptions.showall then -- add a collapsed list of pages which might appear local listtext = pageOptions.list if listtext == "" then listtext = "Other articles" end text = text .. "{{collapse top|title={{resize|85%|" ..listtext .. "}}|bg=fff}}{{hlist" for _, p in pairs(pageNames) do if mw.ustring.match(p, "%S") then text = text .. "|[[" .. mw.text.trim(p) .. "]]" end end text = text .. "}}\n{{collapse bottom}}" end return text end -- Shared template invocation code for lead and random functions local function invoke(frame, template) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articleCount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articleCount < 1 and not (template == "selected" and args[template] and args[args[template]]) then return err("No articles provided") end local pageNames = {} if template == "lead" then pageNames = { args[1] } elseif template == "linked" or template == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getSection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if template == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pageNames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pageNames, p) end end elseif template == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pageNames, p) end end elseif template == "selected" then local articleKey = args[template] if tonumber(articleKey) then -- normalise article number into the range 1..#args articleKey = articleKey % articleCount if articleKey == 0 then articleKey = articleCount end end pageNames = { args[articleKey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberFlags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberFlags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = "" if options.showall then local separator = "" for _, p in pairs(pageNames) do local t = main({ p }, options) if t ~= "" then text = text .. separator .. t separator = options.showall if separator == "" then separator = "{{clear}}{{hr}}" end end end else text = main(pageNames, options) end if text == "" and d.brokenCategory and d.brokenCategory ~= "" and mw.title.getCurrentTitle().isContentPage then return "[[Category:" .. d.brokenCategory .. "]]" else return frame:preprocess(text) end end -- Replicate {{Excerpt}} entirely in Lua for reduced Post-expand include size local function excerpt(frame) local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template local tag = is(args.tag) and args.tag or 'div' local article = is(args.article) and args.article or args[1] or '{{{1}}}' local section = is(args.section) and args.section or args[2] local output = {} output[1] = frame:extensionTag{ name = 'templatestyles', args = {src='Excerpt/styles.css'} } output[2] = '<' .. tag .. ' class="excerpt-block">' output[3] = is(args.indicator) and ('<' .. tag .. ' class="excerpt-indicator">') or '' if is(args.nohat) then output[4] = '' else local hatnote = {} hatnote[1] = 'This' .. (is(args.indicator) and '' or ' section') .. ' is an excerpt from ' hatnote[2] = '[[' hatnote[3] = article .. (is(section) and ('#' .. frame:callParserFunction( 'urlencode', section, 'WIKI' )) or '') hatnote[4] = '|' hatnote[5] = article .. (is(section) and (frame:callParserFunction( '#tag:nowiki', ' § ' ) .. section) or '') hatnote[6] = ']]' hatnote[7] = "''" .. '<span class="mw-editsection-like plainlinks"><span>[ </span>[' local title = mw.title.new(article) or mw.title.getCurrentTitle() hatnote[8] = title:fullUrl('action=edit') .. ' edit' hatnote[9] = ']<span> ]</span></span>' .. "''" output[4] = require('Module:Hatnote')._hatnote(table.concat(hatnote), {selfref=true}) or err("Error generating hatnote") end output[5] = '<' .. tag .. ' class="excerpt">\n' if article ~= '{{{1}}}' then local options = args -- turn template arguments into module options options.paraflags = args.paragraphs options.fileflags = args.files or 1 options.nobold = 1 options.fragment = args.fragment options.keepTables = args.tables or 1 options.keepRefs = args.references or 1 options.keepSubsections = args.subsections local pageNames = { (article .. '#' .. (section or '')) } local text = main(pageNames, options) if text == "" and d.brokenCategory and d.brokenCategory ~= "" and mw.title.getCurrentTitle().isContentPage then output[6] = "[[Category:" .. d.brokenCategory .. "]]" else output[6] = frame:preprocess(text) or err("Error processing text") end else output[6] = err("No article provided") end output[7] = '</' .. tag .. '>' output[8] = is(args.indicator) and ('</' .. tag .. '>') or '' output[9] = '</' .. tag .. '>' output[10] = mw.title.getCurrentTitle().isContentPage and '[[Category:Articles with excerpts]]' or '' return table.concat(output) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter function p.excerpt(frame) return excerpt(frame) end -- {{Excerpt}} transcludes part of an article into another article -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getSection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argImage(text) end function p.checkimage(image) return checkImage(image) end function p.parseimage(text, start) return parseImage(text, start) end function p.cleanupText(text, options) return cleanupText(text, options) end function p.main(pageNames, options) return main(pageNames, options) end function p.numberflags(str) return numberFlags(str) end return p tsy316l99ffc2adax9n0zno1dixtsi1 797329 797328 2020-05-13T13:22:38Z en>Sophivorus 0 Update from latest sandbox version 797329 Scribunto text/plain -- Get localized data local d = require("Module:Excerpt/i18n") local p = {} -- Helper function to debug -- Returns blank text or an error message if requested local errors local function err(msg, a, b) local text = mw.ustring.format(d.error[msg] or msg or "", a, b) if errors then error(text, 2) end return "" end -- Helper function to test for truthy and falsy values local function is(value) if not value or value == "" or value == "0" or value == "false" or value == "no" then return false end return true end -- Helper function to match from a list regular expressions -- Like so: match pre..list[1]..post or pre..list[2]..post or ... local function matchAny(text, pre, list, post, init) local match = {} for i = 1, #list do match = { mw.ustring.match(text, pre .. list[i] .. post, init) } if match[1] then return unpack(match) end end return nil end -- Helper function to convert imagemaps into standard images local function convertImageMap(imagemap) local image = matchAny(imagemap, "[>\n]%s*", d.fileNamespaces, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Helper function to convert a comma-separated list of numbers or min-max ranges into a list of booleans -- For example: "1,3-5" to {1=true,2=false,3=true,4=true,5=true} local function numberFlags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Helper function to convert template arguments into an array of arguments fit for main() local function parseArgs(frame) local args = {} for key, value in pairs(frame:getParent().args) do args[key] = value end for key, value in pairs(frame.args) do args[key] = value end -- args from a Lua call have priority over parent args from template args.paraflags = numberFlags(args["paragraphs"] or "") -- parse paragraphs: "1,3-5" to {"1","3-5"} args.fileflags = numberFlags(args["files"] or "") -- parse file numbers return args end -- Helper function to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function stripTemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchAny(t, "^{{%s*", d.unwantedInlineTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noRef = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noRef = mw.ustring.gsub(noRef, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noRef = mw.ustring.sub(noRef, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noRef, 3), "%b{}", stripTemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noRef = mw.ustring.gsub(noRef, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noRef = mw.ustring.gsub(noRef, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noRef ~= t then return noRef end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects, and processing file description pages for files -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local target = title.redirectTarget if target then title = target end return title:getContent(), title.prefixedText end -- Return the target of the redirect, -- or the same title if it's not a redirect -- or nil if the title was not found local function getTarget(page) local title = mw.title.new(page) if title then local target = title.redirectTarget if target then return target.prefixedText end return title.prefixedText end end -- Check image for suitability local function checkImage(image) local page = matchAny(image, "", d.fileNamespaces, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchAny(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local fileDescription, fileTitle = getContent(page) -- get file description and title after following any redirect if fileDescription and fileDescription ~= "" then -- found description on local wiki if mw.ustring.match(fileDescription, "[Nn]on%-free") then return false end fileDescription = mw.ustring.gsub(fileDescription, "%b{}", stripTemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess elseif not fileTitle then return false else -- try commons fileDescription = "{{" .. fileTitle .. "}}" end frame = frame or mw.getCurrentFrame() fileDescription = frame:preprocess(fileDescription) return ( fileDescription and fileDescription ~= "" and not mw.ustring.match(fileDescription, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseImage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchAny(text, startre .. "%[%[%s*", d.fileNamespaces, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parseCaption(caption) if not caption then return nil end local length = mw.ustring.len(caption) local position = 1 while position <= length do local linkStart, linkEnd = mw.ustring.find(caption, "%b[]", position) linkStart = linkStart or length + 1 -- avoid comparison with nil when no link local templateStart, templateEnd = mw.ustring.find(caption, "%b{}", position) templateStart = templateStart or length + 1 -- avoid comparison with nil when no template local argEnd = mw.ustring.find(caption, "[|}]", position) or length + 1 if linkStart < templateStart and linkStart < argEnd then position = linkEnd + 1 -- skip wikilink elseif templateStart < argEnd then position = templateEnd + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argEnd - 1) end end return caption -- No terminator found: return entire caption end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argImage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local captureFrom = 1 while captureFrom < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", captureFrom) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgName = mw.ustring.lower(argname) if mw.ustring.find(lcArgName, "caption") or mw.ustring.find(lcArgName, "size") or mw.ustring.find(lcArgName, "upright") then image = nil end end if image then hasImages = true images[position] = image captureFrom = position else captureFrom = mw.ustring.len(text) end end captureFrom = 1 while captureFrom < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", captureFrom) if image then hasImages = true images[position] = image captureFrom = position else captureFrom = mw.ustring.len(text) end end captureFrom = 1 while captureFrom < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", captureFrom) if image then hasImages = true if not images[position] then images[position] = image end captureFrom = position else captureFrom = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} captureFrom = 1 while captureFrom < mw.ustring.len(text) do local position, caption = matchAny(text, "|%s*", d.captionParams, "%s*=%s*()([^\n]+)", captureFrom) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position) if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parseCaption(caption) end end end captureFrom = position else captureFrom = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookFrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local length = mw.ustring.len(altText) local afterText = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookFrom) or length+1, mw.ustring.match(altText, "()|", lookFrom) or length+1) altText = mw.ustring.sub(altText, 1, afterText-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseImage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchAny(image, "^", d.fileNamespaces, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end local function modifyImage(image, fileArgs) if fileArgs then for _, filearg in pairs(mw.text.split(fileArgs, "|")) do -- handle fileArgs=left|border etc. local fa = mw.ustring.gsub(filearg, "=.*", "") -- "upright=0.75" → "upright" local group = {fa} -- group of "border" is ["border"]... for _, g in pairs(d.imageParams) do for _, a in pairs(g) do if fa == a then group = g end -- ...but group of "left" is ["right", "left", "center", "none"] end end for _, a in pairs(group) do image = mw.ustring.gsub(image, "|%s*" .. a .. "%f[%A]%s*=[^|%]]*", "") -- remove "|upright=0.75" etc. image = mw.ustring.gsub(image, "|%s*" .. a .. "%s*([|%]])", "%1") -- replace "|left|" by "|" etc. end image = mw.ustring.gsub(image, "([|%]])", "|" .. filearg .. "%1", 1) -- replace "|" by "|left|" etc. end end return image end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allParagraphs = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberFlags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allParagraphs = false end -- if any para specifically requested, don't keep all end end if filesOnly then allParagraphs = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberFlags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileArgs = options.fileargs and mw.text.trim(options.fileargs) if fileArgs == '' then fileArgs = nil end local leadStart = nil -- have we found some text yet? local t = "" -- the stripped down output text local fileText = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space -- Add named files local f = options.files if f and mw.ustring.match(f, "[^%d%s%-,]") then -- filename rather than number list f = mw.ustring.gsub(f, "^%s*File%s*:%s*", "", 1) f = mw.ustring.gsub(f, "^%s*Image%s*:%s*", "", 1) f = "[[File:" .. f .. "]]" f = modifyImage(f, "thumb") f = modifyImage(f, fileArgs) if checkImage(f) then fileText = fileText .. f .. "\n" end end repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} or {| Table |} if not leadStart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchAny(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadStart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif matchAny(token, "{{%s*", d.wantedBlockTemplates, "%s*%f[|}]") then t = t .. token -- keep wanted block templates elseif is(options.keepTables) and mw.ustring.sub(token, 1, 2) == '{|' then t = t .. token -- keep tables elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argImage(token) or {} if not images then local image = parseImage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkImage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = modifyImage(image, "thumb") image = modifyImage(image, fileArgs) fileText = fileText .. image end end end end else -- the next token in text is not a template token = parseImage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkImage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original image = modifyImage(image, fileArgs) fileText = fileText .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterEnd = mw.ustring.len(text) + 1 local blankPosition = mw.ustring.find(text, "\n%s*\n") or afterEnd -- position of next paragraph delimiter (or end of text) local endPosition = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterEnd, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterEnd, blankPosition) token = mw.ustring.sub(text, 1, endPosition-1) if blankPosition < afterEnd and blankPosition == endPosition then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankPosition) end local isHatnote = not(leadStart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadStart = leadStart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allParagraphs or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$") -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return fileText, text end local function cleanupText(text, options) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits if mw.ustring.find(text, "[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]") then -- avoid expensive search if possible text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text between onlyinclude sections text = mw.ustring.gsub(text, "^.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text before first onlyinclude section text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.*", "") -- remove text after last onlyinclude section end if not is(options.keepSubsections) then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if the lead is empty end if not is(options.keepRefs) then text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "%b{}", stripTemplate) -- remove unwanted templates such as references end text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImageMap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getSection(text, section, mainOnly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextSection if mainOnly then nextSection = "\n==.*" -- Main part of section terminates at any level of header else nextSection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextSection, "") -- remove later sections with headings at this level or higher return content end -- Remove unmatched <tag> or </tag> tags local function fixTags(text, tag) local startCount = 0 for i in mw.ustring.gmatch(text, "<%s*" .. tag .. "%f[^%w_].->") do startCount = startCount + 1 end local endCount = 0 for i in mw.ustring.gmatch(text, "<%s*/" .. tag .. "%f[^%w_].->") do endCount = endCount + 1 end if startCount > endCount then -- more <tag> than </tag>: remove the last few <tag>s local i = 0 text = mw.ustring.gsub(text, "<%s*" .. tag .. "%f[^%w_].->", function(t) i = i + 1 if i > endCount then return "" else return nil end end) -- "end" here terminates the anonymous replacement function(t) passed to gsub elseif endCount > startCount then -- more </tag> than <tag>: remove the first few </tag>s text = mw.ustring.gsub(text, "<%s*/" .. tag .. "%f[^%w_].->", "", endCount - startCount) end return text end -- Main function returns a string value: text of the lead of a page local function main(pageNames, options) if not pageNames or #pageNames < 1 then return err("pageNames") end local pageName local text local pageCount = #pageNames local firstPage = pageNames[1] or "(nil)" -- save for error message, as it the name will be deleted local gotOptions local pageOptionsString local section -- read the page, or a random one if multiple pages were provided if pageCount > 1 then math.randomseed(os.time()) end while not text and pageCount > 0 do local pageNumber = 1 if pageCount > 1 then pageNumber = math.random(pageCount) end -- pick a random title pageName = pageNames[pageNumber] if pageName and pageName ~= "" then -- We have page or [[page]] or [[page|text]], possibly followed by |opt1|opt2... local pn pn, gotOptions, pageOptionsString = mw.ustring.match(pageName, "^%s*(%[%b[]%])%s*(|?)(.*)") if pn then pageName = mw.ustring.match(pn, "%[%[([^|%]]*)") -- turn [[page|text]] into page, discarding text else -- we have page or page|opt... pageName, gotOptions, pageOptionsString = mw.ustring.match(pageName, "%s*([^|]*[^|%s])%s*(|?)(.*)") end if pageName and pageName ~= "" then local pn pn, section = mw.ustring.match(pageName, "(.-)#(.*)") pageName = pn or pageName text, normalisedPageName = getContent(pageName) if is(options.fragment) then local frame = mw.getCurrentFrame() text = frame:callParserFunction('#lst', normalisedPageName, options.fragment) end if not normalisedPageName then return err("noTitle", pageName) else pageName = normalisedPageName end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pageName, ".-#(.*)") -- parse redirect to Page#Section end if text and section and section ~= "" then text = getSection(text, section) end end end if not text then table.remove(pageNames, pageNumber) end -- this one didn't work; try another pageCount = pageCount - 1 -- ensure that we exit the loop after at most #pageNames iterations end if not text then return err("firstPage", firstPage) end text = cleanupText(text, options) local pageOptions = {} -- pageOptions (even if value is "") have priority over global options for k, v in pairs(options) do pageOptions[k] = v end if gotOptions and gotOptions ~= "" then for _, t in pairs(mw.text.split(pageOptionsString, "|")) do local k, v = mw.ustring.match(t, "%s*([^=]-)%s*=(.-)%s*$") pageOptions[k] = v end pageOptions.paraflags = numberFlags(pageOptions["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} pageOptions.fileflags = numberFlags(pageOptions["files"] or "") -- parse file numbers if pageOptions.more and pageOptions.more == "" then pageOptions.more = "Read more..." end -- more= is short for this default text end local fileText fileText, text = parse(text, pageOptions) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local position = mw.ustring.find(text, "'''" .. lang:ucfirst(pageName) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pageName) .. "'''", 1, true) -- plain search: special characters in pageName represent themselves if position then local length = mw.ustring.len(pageName) text = mw.ustring.sub(text, 1, position + 2) .. "[[" .. mw.ustring.sub(text, position + 3, position + length + 2) .. "]]" .. mw.ustring.sub(text, position + length + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "()'''(.-'*)'''", function(a, b) if a < 100 and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return "'''[[" .. pageName .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pageName|Foo]]''' else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- remove '''bold text''' if requested if is(pageOptions.nobold) then text = mw.ustring.gsub(text, "'''", "") end text = fileText .. text -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27") -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2") -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27") until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", "") -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", "") -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", "") -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. -- Ensure div tags match text = fixTags(text, "div") if pageOptions.more then text = text .. " '''[[" .. pageName .. "|" .. pageOptions.more .. "]]'''" end -- wikilink to article for more info if pageOptions.list and not pageOptions.showall then -- add a collapsed list of pages which might appear local listtext = pageOptions.list if listtext == "" then listtext = "Other articles" end text = text .. "{{collapse top|title={{resize|85%|" ..listtext .. "}}|bg=fff}}{{hlist" for _, p in pairs(pageNames) do if mw.ustring.match(p, "%S") then text = text .. "|[[" .. mw.text.trim(p) .. "]]" end end text = text .. "}}\n{{collapse bottom}}" end return text end -- Main invocation function for templates local function lead(frame) local args = parseArgs(frame) local pageNames = { args[1] } local text = main(pageNames, args) if text == "" and d.brokenCategory and d.brokenCategory ~= "" and mw.title.getCurrentTitle().isContentPage then return "[[Category:" .. d.brokenCategory .. "]]" else return frame:preprocess(text) end end -- Entry points for templates function p.lead(frame) return lead(frame) end function p.target(frame) return getTarget(frame.args[1]) end -- Entry points for other Lua modules function p.getTarget(page) return getTarget(page) end function p.getContent(page, frame) return getContent(page, frame) end function p.getSection(text, section) return getSection(text, section) end function p.getsection(text, section) return getSection(text, section) end -- Temporary entry point for backwards compatibility function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.parseImage(text, start) return parseImage(text, start) end function p.parseimage(text, start) return parseImage(text, start) end -- Temporary entry point for backwards compatibility function p.parseArgs(frame) return parseArgs(frame) end function p.argImage(text) return argImage(text) end function p.argimage(text) return argImage(text) end -- Temporary entry point for backwards compatibility function p.checkImage(image) return checkImage(image) end function p.checkimage(image) return checkImage(image) end -- Temporary entry point for backwards compatibility function p.cleanupText(text, options) return cleanupText(text, options) end function p.main(pageNames, options) return main(pageNames, options) end function p.err(msg, a, b) return err(msg, a, b) end function p.is(value) return is(value) end function p.numberFlags(str) return numberFlags(str) end function p.numberflags(str) return numberFlags(str) end -- Temporary entry point for backwards compatibility return p puserc5ig1ek0mdeydgzpldm4galznr 797330 797329 2020-05-13T13:23:23Z en>Pppery 0 Please stop misusing your global interface editor access to edit template-protected templates 797330 Scribunto text/plain -- Get localized data local d = require("Module:Excerpt/i18n") local p = {} -- Helper function to debug -- Returns blank text or an error message if requested local errors local function err(msg,a,b) local text = mw.ustring.format(d.error[msg] or msg or '',a,b) if errors then error(text, 2) end return "" end -- Helper function to test for truthy and falsy values local function is(value) if not value or value == "" or value == "0" or value == "false" or value == "no" then return false end return true end -- Helper function to match from a list regular expressions -- Like so: match pre..list[1]..post or pre..list[2]..post or ... local function matchAny(text, pre, list, post, init) local match = {} for i = 1, #list do match = { mw.ustring.match(text, pre .. list[i] .. post, init) } if match[1] then return unpack(match) end end return nil end -- Help gsub to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function stripTemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchAny(t, "^{{%s*", d.unwantedInlineTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noRef = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noRef = mw.ustring.gsub(noRef, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noRef = mw.ustring.sub(noRef, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noRef, 3), "%b{}", stripTemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noRef = mw.ustring.gsub(noRef, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noRef = mw.ustring.gsub(noRef, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noRef ~= t then return noRef end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local target = title.redirectTarget if target then title = target end return title:getContent(), title.prefixedText end -- Check image for suitability local function checkImage(image) local page = matchAny(image, "", d.fileNamespaces, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchAny(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local fileDescription, fileTitle = getContent(page) -- get file description and title after following any redirect if fileDescription and fileDescription ~= "" then -- found description on local wiki if mw.ustring.match(fileDescription, "[Nn]on%-free") then return false end fileDescription = mw.ustring.gsub(fileDescription, "%b{}", stripTemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess elseif not fileTitle then return false else -- try commons fileDescription = "{{" .. fileTitle .. "}}" end frame = frame or mw.getCurrentFrame() fileDescription = frame:preprocess(fileDescription) return ( fileDescription and fileDescription ~= "" and not mw.ustring.match(fileDescription, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseImage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchAny(text, startre .. "%[%[%s*", d.fileNamespaces, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parseCaption(caption) if not caption then return nil end local length = mw.ustring.len(caption) local position = 1 while position <= length do local linkStart, linkEnd = mw.ustring.find(caption, "%b[]", position) linkStart = linkStart or length + 1 -- avoid comparison with nil when no link local templateStart, templateEnd = mw.ustring.find(caption, "%b{}", position) templateStart = templateStart or length + 1 -- avoid comparison with nil when no template local argEnd = mw.ustring.find(caption, "[|}]", position) or length + 1 if linkStart < templateStart and linkStart < argEnd then position = linkEnd + 1 -- skip wikilink elseif templateStart < argEnd then position = templateEnd + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argEnd - 1) end end return caption -- No terminator found: return entire caption end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argImage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local captureFrom = 1 while captureFrom < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", captureFrom) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgName = mw.ustring.lower(argname) if mw.ustring.find(lcArgName, "caption") or mw.ustring.find(lcArgName, "size") or mw.ustring.find(lcArgName, "upright") then image = nil end end if image then hasImages = true images[position] = image captureFrom = position else captureFrom = mw.ustring.len(text) end end captureFrom = 1 while captureFrom < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", captureFrom) if image then hasImages = true images[position] = image captureFrom = position else captureFrom = mw.ustring.len(text) end end captureFrom = 1 while captureFrom < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", captureFrom) if image then hasImages = true if not images[position] then images[position] = image end captureFrom = position else captureFrom = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} captureFrom = 1 while captureFrom < mw.ustring.len(text) do local position, caption = matchAny(text, "|%s*", d.captionParams, "%s*=%s*()([^\n]+)", captureFrom) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position) if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parseCaption(caption) end end end captureFrom = position else captureFrom = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookFrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local length = mw.ustring.len(altText) local afterText = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookFrom) or length+1, mw.ustring.match(altText, "()|", lookFrom) or length+1) altText = mw.ustring.sub(altText, 1, afterText-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseImage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchAny(image, "^", d.fileNamespaces, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImageMap(imagemap) local image = matchAny(imagemap, "[>\n]%s*", d.fileNamespaces, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberFlags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end local imageArgGroups = { {"thumb", "thumbnail", "frame", "framed", "frameless"}, {"right", "left", "center", "none"}, {"baseline", "middle", "sub", "super", "text-top", "text-bottom", "top", "bottom"} } local function modifyImage(image, fileArgs) if fileArgs then for _, filearg in pairs(mw.text.split(fileArgs, "|")) do -- handle fileArgs=left|border etc. local fa = mw.ustring.gsub(filearg, "=.*", "") -- "upright=0.75" → "upright" local group = {fa} -- group of "border" is ["border"]... for _, g in pairs(imageArgGroups) do for _, a in pairs(g) do if fa == a then group = g end -- ...but group of "left" is ["right", "left", "center", "none"] end end for _, a in pairs(group) do image = mw.ustring.gsub(image, "|%s*" .. a .. "%f[%A]%s*=[^|%]]*", "") -- remove "|upright=0.75" etc. image = mw.ustring.gsub(image, "|%s*" .. a .. "%s*([|%]])", "%1") -- replace "|left|" by "|" etc. end image = mw.ustring.gsub(image, "([|%]])", "|" .. filearg .. "%1", 1) -- replace "|" by "|left|" etc. end end return image end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allParagraphs = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberFlags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allParagraphs = false end -- if any para specifically requested, don't keep all end end if filesOnly then allParagraphs = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberFlags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileArgs = options.fileargs and mw.text.trim(options.fileargs) if fileArgs == '' then fileArgs = nil end local leadStart = nil -- have we found some text yet? local t = "" -- the stripped down output text local fileText = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space -- Add named files local f = options.files if f and mw.ustring.match(f, "[^%d%s%-,]") then -- filename rather than number list f = mw.ustring.gsub(f, "^%s*File%s*:%s*", "", 1) f = mw.ustring.gsub(f, "^%s*Image%s*:%s*", "", 1) f = "[[File:" .. f .. "]]" f = modifyImage(f, "thumb") f = modifyImage(f, fileArgs) if checkImage(f) then fileText = fileText .. f .. "\n" end end repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} or {| Table |} if not leadStart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchAny(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadStart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif matchAny(token, "{{%s*", d.wantedBlockTemplates, "%s*%f[|}]") then t = t .. token -- keep wanted block templates elseif is(options.keepTables) and mw.ustring.sub(token, 1, 2) == '{|' then t = t .. token -- keep tables elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argImage(token) or {} if not images then local image = parseImage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkImage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = modifyImage(image, "thumb") image = modifyImage(image, fileArgs) fileText = fileText .. image end end end end else -- the next token in text is not a template token = parseImage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkImage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original image = modifyImage(image, fileArgs) fileText = fileText .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterEnd = mw.ustring.len(text) + 1 local blankPosition = mw.ustring.find(text, "\n%s*\n") or afterEnd -- position of next paragraph delimiter (or end of text) local endPosition = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterEnd, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterEnd, blankPosition) token = mw.ustring.sub(text, 1, endPosition-1) if blankPosition < afterEnd and blankPosition == endPosition then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankPosition) end local isHatnote = not(leadStart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadStart = leadStart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allParagraphs or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$") -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return fileText, text end local function cleanupText(text, options) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits if mw.ustring.find(text, "[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]") then -- avoid expensive search if possible text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text between onlyinclude sections text = mw.ustring.gsub(text, "^.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text before first onlyinclude section text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.*", "") -- remove text after last onlyinclude section end if not is(options.keepSubsections) then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if the lead is empty end if not is(options.keepRefs) then text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "%b{}", stripTemplate) -- remove unwanted templates such as references end text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImageMap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getSection(text, section, mainOnly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextSection if mainOnly then nextSection = "\n==.*" -- Main part of section terminates at any level of header else nextSection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextSection, "") -- remove later sections with headings at this level or higher return content end -- Remove unmatched <tag> or </tag> tags local function fixTags(text, tag) local startCount = 0 for i in mw.ustring.gmatch(text, "<%s*" .. tag .. "%f[^%w_].->") do startCount = startCount + 1 end local endCount = 0 for i in mw.ustring.gmatch(text, "<%s*/" .. tag .. "%f[^%w_].->") do endCount = endCount + 1 end if startCount > endCount then -- more <tag> than </tag>: remove the last few <tag>s local i = 0 text = mw.ustring.gsub(text, "<%s*" .. tag .. "%f[^%w_].->", function(t) i = i + 1 if i > endCount then return "" else return nil end end) -- "end" here terminates the anonymous replacement function(t) passed to gsub elseif endCount > startCount then -- more </tag> than <tag>: remove the first few </tag>s text = mw.ustring.gsub(text, "<%s*/" .. tag .. "%f[^%w_].->", "", endCount - startCount) end return text end -- Main function returns a string value: text of the lead of a page local function main(pageNames, options) if not pageNames or #pageNames < 1 then return err("No page names given") end local pageName local text local pageCount = #pageNames local firstPage = pageNames[1] or "(nil)" -- save for error message, as it the name will be deleted local gotOptions local pageOptionsString local section -- read the page, or a random one if multiple pages were provided if pageCount > 1 then math.randomseed(os.time()) end while not text and pageCount > 0 do local pageNumber = 1 if pageCount > 1 then pageNumber = math.random(pageCount) end -- pick a random title pageName = pageNames[pageNumber] if pageName and pageName ~= "" then -- We have page or [[page]] or [[page|text]], possibly followed by |opt1|opt2... local pn pn, gotOptions, pageOptionsString = mw.ustring.match(pageName, "^%s*(%[%b[]%])%s*(|?)(.*)") if pn then pageName = mw.ustring.match(pn, "%[%[([^|%]]*)") -- turn [[page|text]] into page, discarding text else -- we have page or page|opt... pageName, gotOptions, pageOptionsString = mw.ustring.match(pageName, "%s*([^|]*[^|%s])%s*(|?)(.*)") end if pageName and pageName ~= "" then local pn pn, section = mw.ustring.match(pageName, "(.-)#(.*)") pageName = pn or pageName text, normalisedPageName = getContent(pageName) if is(options.fragment) then local frame = mw.getCurrentFrame() text = frame:callParserFunction('#lst', normalisedPageName, options.fragment) end if not normalisedPageName then return err("No title for page name " .. pageName) else pageName = normalisedPageName end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pageName, ".-#(.*)") -- parse redirect to Page#Section end if text and section and section ~= "" then text = getSection(text, section) end end end if not text then table.remove(pageNames, pageNumber) end -- this one didn't work; try another pageCount = pageCount - 1 -- ensure that we exit the loop after at most #pageNames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstPage) end text = cleanupText(text, options) local pageOptions = {} -- pageOptions (even if value is "") have priority over global options for k, v in pairs(options) do pageOptions[k] = v end if gotOptions and gotOptions ~= "" then for _, t in pairs(mw.text.split(pageOptionsString, "|")) do local k, v = mw.ustring.match(t, "%s*([^=]-)%s*=(.-)%s*$") pageOptions[k] = v end pageOptions.paraflags = numberFlags(pageOptions["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} pageOptions.fileflags = numberFlags(pageOptions["files"] or "") -- parse file numbers if pageOptions.more and pageOptions.more == "" then pageOptions.more = "Read more..." end -- more= is short for this default text end local fileText fileText, text = parse(text, pageOptions) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pageName) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pageName) .. "'''", 1, true) -- plain search: special characters in pageName represent themselves if pos then local len = mw.ustring.len(pageName) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "()'''(.-'*)'''", function(a, b) if a < 100 and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return "'''[[" .. pageName .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pageName|Foo]]''' else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- remove '''bold text''' if requested if is(pageOptions.nobold) then text = mw.ustring.gsub(text, "'''", "") end text = fileText .. text -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27") -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2") -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27") until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", "") -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", "") -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", "") -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. -- Ensure div tags match text = fixTags(text, "div") if pageOptions.more then text = text .. " '''[[" .. pageName .. "|" .. pageOptions.more .. "]]'''" end -- wikilink to article for more info if pageOptions.list and not pageOptions.showall then -- add a collapsed list of pages which might appear local listtext = pageOptions.list if listtext == "" then listtext = "Other articles" end text = text .. "{{collapse top|title={{resize|85%|" ..listtext .. "}}|bg=fff}}{{hlist" for _, p in pairs(pageNames) do if mw.ustring.match(p, "%S") then text = text .. "|[[" .. mw.text.trim(p) .. "]]" end end text = text .. "}}\n{{collapse bottom}}" end return text end -- Shared template invocation code for lead and random functions local function invoke(frame, template) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articleCount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articleCount < 1 and not (template == "selected" and args[template] and args[args[template]]) then return err("No articles provided") end local pageNames = {} if template == "lead" then pageNames = { args[1] } elseif template == "linked" or template == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getSection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if template == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pageNames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pageNames, p) end end elseif template == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pageNames, p) end end elseif template == "selected" then local articleKey = args[template] if tonumber(articleKey) then -- normalise article number into the range 1..#args articleKey = articleKey % articleCount if articleKey == 0 then articleKey = articleCount end end pageNames = { args[articleKey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberFlags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberFlags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = "" if options.showall then local separator = "" for _, p in pairs(pageNames) do local t = main({ p }, options) if t ~= "" then text = text .. separator .. t separator = options.showall if separator == "" then separator = "{{clear}}{{hr}}" end end end else text = main(pageNames, options) end if text == "" and d.brokenCategory and d.brokenCategory ~= "" and mw.title.getCurrentTitle().isContentPage then return "[[Category:" .. d.brokenCategory .. "]]" else return frame:preprocess(text) end end -- Replicate {{Excerpt}} entirely in Lua for reduced Post-expand include size local function excerpt(frame) local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template local tag = is(args.tag) and args.tag or 'div' local article = is(args.article) and args.article or args[1] or '{{{1}}}' local section = is(args.section) and args.section or args[2] local output = {} output[1] = frame:extensionTag{ name = 'templatestyles', args = {src='Excerpt/styles.css'} } output[2] = '<' .. tag .. ' class="excerpt-block">' output[3] = is(args.indicator) and ('<' .. tag .. ' class="excerpt-indicator">') or '' if is(args.nohat) then output[4] = '' else local hatnote = {} hatnote[1] = 'This' .. (is(args.indicator) and '' or ' section') .. ' is an excerpt from ' hatnote[2] = '[[' hatnote[3] = article .. (is(section) and ('#' .. frame:callParserFunction( 'urlencode', section, 'WIKI' )) or '') hatnote[4] = '|' hatnote[5] = article .. (is(section) and (frame:callParserFunction( '#tag:nowiki', ' § ' ) .. section) or '') hatnote[6] = ']]' hatnote[7] = "''" .. '<span class="mw-editsection-like plainlinks"><span>[ </span>[' local title = mw.title.new(article) or mw.title.getCurrentTitle() hatnote[8] = title:fullUrl('action=edit') .. ' edit' hatnote[9] = ']<span> ]</span></span>' .. "''" output[4] = require('Module:Hatnote')._hatnote(table.concat(hatnote), {selfref=true}) or err("Error generating hatnote") end output[5] = '<' .. tag .. ' class="excerpt">\n' if article ~= '{{{1}}}' then local options = args -- turn template arguments into module options options.paraflags = args.paragraphs options.fileflags = args.files or 1 options.nobold = 1 options.fragment = args.fragment options.keepTables = args.tables or 1 options.keepRefs = args.references or 1 options.keepSubsections = args.subsections local pageNames = { (article .. '#' .. (section or '')) } local text = main(pageNames, options) if text == "" and d.brokenCategory and d.brokenCategory ~= "" and mw.title.getCurrentTitle().isContentPage then output[6] = "[[Category:" .. d.brokenCategory .. "]]" else output[6] = frame:preprocess(text) or err("Error processing text") end else output[6] = err("No article provided") end output[7] = '</' .. tag .. '>' output[8] = is(args.indicator) and ('</' .. tag .. '>') or '' output[9] = '</' .. tag .. '>' output[10] = mw.title.getCurrentTitle().isContentPage and '[[Category:Articles with excerpts]]' or '' return table.concat(output) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter function p.excerpt(frame) return excerpt(frame) end -- {{Excerpt}} transcludes part of an article into another article -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getSection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argImage(text) end function p.checkimage(image) return checkImage(image) end function p.parseimage(text, start) return parseImage(text, start) end function p.cleanupText(text, options) return cleanupText(text, options) end function p.main(pageNames, options) return main(pageNames, options) end function p.numberflags(str) return numberFlags(str) end return p tsy316l99ffc2adax9n0zno1dixtsi1 797331 797330 2020-05-25T13:11:51Z en>Sophivorus 0 Critical bugfix: regex was matching nested templates as well as the main template being evaluated 797331 Scribunto text/plain -- Get localized data local d = require("Module:Excerpt/i18n") local p = {} -- Helper function to debug -- Returns blank text or an error message if requested local errors local function err(msg,a,b) local text = mw.ustring.format(d.error[msg] or msg or '',a,b) if errors then error(text, 2) end return "" end -- Helper function to test for truthy and falsy values local function is(value) if not value or value == "" or value == "0" or value == "false" or value == "no" then return false end return true end -- Helper function to match from a list regular expressions -- Like so: match pre..list[1]..post or pre..list[2]..post or ... local function matchAny(text, pre, list, post, init) local match = {} for i = 1, #list do match = { mw.ustring.match(text, pre .. list[i] .. post, init) } if match[1] then return unpack(match) end end return nil end -- Help gsub to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function stripTemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchAny(t, "^{{%s*", d.unwantedInlineTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noRef = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noRef = mw.ustring.gsub(noRef, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noRef = mw.ustring.sub(noRef, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noRef, 3), "%b{}", stripTemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noRef = mw.ustring.gsub(noRef, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noRef = mw.ustring.gsub(noRef, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noRef ~= t then return noRef end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects, and processing file description pages for files. -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found local function getContent(page, frame) local title = mw.title.new(page) -- Read description page (for :File:Foo rather than File:Foo) if not title then return false, false end local target = title.redirectTarget if target then title = target end return title:getContent(), title.prefixedText end -- Check image for suitability local function checkImage(image) local page = matchAny(image, "", d.fileNamespaces, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg audio etc.) if not matchAny(page, "%.", {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"}, "%s*$") then return false end local fileDescription, fileTitle = getContent(page) -- get file description and title after following any redirect if fileDescription and fileDescription ~= "" then -- found description on local wiki if mw.ustring.match(fileDescription, "[Nn]on%-free") then return false end fileDescription = mw.ustring.gsub(fileDescription, "%b{}", stripTemplate) -- remove DEFAULTSORT etc. to avoid side effects of frame:preprocess elseif not fileTitle then return false else -- try commons fileDescription = "{{" .. fileTitle .. "}}" end frame = frame or mw.getCurrentFrame() fileDescription = frame:preprocess(fileDescription) return ( fileDescription and fileDescription ~= "" and not mw.ustring.match(fileDescription, "[Nn]on%-free") ) and true or false -- hide non-free image end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseImage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchAny(text, startre .. "%[%[%s*", d.fileNamespaces, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parseCaption(caption) if not caption then return nil end local length = mw.ustring.len(caption) local position = 1 while position <= length do local linkStart, linkEnd = mw.ustring.find(caption, "%b[]", position) linkStart = linkStart or length + 1 -- avoid comparison with nil when no link local templateStart, templateEnd = mw.ustring.find(caption, "%b{}", position) templateStart = templateStart or length + 1 -- avoid comparison with nil when no template local argEnd = mw.ustring.find(caption, "[|}]", position) or length + 1 if linkStart < templateStart and linkStart < argEnd then position = linkEnd + 1 -- skip wikilink elseif templateStart < argEnd then position = templateEnd + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argEnd - 1) end end return caption -- No terminator found: return entire caption end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argImage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local captureFrom = 1 while captureFrom < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", captureFrom) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgName = mw.ustring.lower(argname) if mw.ustring.find(lcArgName, "caption") or mw.ustring.find(lcArgName, "size") or mw.ustring.find(lcArgName, "upright") then image = nil end end if image then hasImages = true images[position] = image captureFrom = position else captureFrom = mw.ustring.len(text) end end captureFrom = 1 while captureFrom < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", captureFrom) if image then hasImages = true images[position] = image captureFrom = position else captureFrom = mw.ustring.len(text) end end captureFrom = 1 while captureFrom < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", captureFrom) if image then hasImages = true if not images[position] then images[position] = image end captureFrom = position else captureFrom = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} captureFrom = 1 while captureFrom < mw.ustring.len(text) do local position, caption = matchAny(text, "|%s*", d.captionParams, "%s*=%s*()([^\n]+)", captureFrom) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position) if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parseCaption(caption) end end end captureFrom = position else captureFrom = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookFrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local length = mw.ustring.len(altText) local afterText = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookFrom) or length+1, mw.ustring.match(altText, "()|", lookFrom) or length+1) altText = mw.ustring.sub(altText, 1, afterText-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseImage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchAny(image, "^", d.fileNamespaces, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end -- Help gsub convert imagemaps into standard images local function convertImageMap(imagemap) local image = matchAny(imagemap, "[>\n]%s*", d.fileNamespaces, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Convert a comma-separated list of numbers or min-max ranges into a list of booleans, e.g. "1,3-5" → {1=true,2=false,3=true,4=true,5=true} local function numberFlags(str) local ranges = mw.text.split(str, ",") -- parse ranges, e.g. "1,3-5" → {"1","3-5"} local flags = {} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" → min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" → min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end local imageArgGroups = { {"thumb", "thumbnail", "frame", "framed", "frameless"}, {"right", "left", "center", "none"}, {"baseline", "middle", "sub", "super", "text-top", "text-bottom", "top", "bottom"} } local function modifyImage(image, fileArgs) if fileArgs then for _, filearg in pairs(mw.text.split(fileArgs, "|")) do -- handle fileArgs=left|border etc. local fa = mw.ustring.gsub(filearg, "=.*", "") -- "upright=0.75" → "upright" local group = {fa} -- group of "border" is ["border"]... for _, g in pairs(imageArgGroups) do for _, a in pairs(g) do if fa == a then group = g end -- ...but group of "left" is ["right", "left", "center", "none"] end end for _, a in pairs(group) do image = mw.ustring.gsub(image, "|%s*" .. a .. "%f[%A]%s*=[^|%]]*", "") -- remove "|upright=0.75" etc. image = mw.ustring.gsub(image, "|%s*" .. a .. "%s*([|%]])", "%1") -- replace "|left|" by "|" etc. end image = mw.ustring.gsub(image, "([|%]])", "|" .. filearg .. "%1", 1) -- replace "|" by "|left|" etc. end end return image end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}`. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. `1,3-5`) or a table (e.g. `{1=true,2=false,3=true,4=true,5=true}` -- options.fileargs : args for the [[File:]] syntax, such as `left` -- @param filesOnly : If set, only return the files and not the prose local function parse(text, options, filesOnly) local allParagraphs = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberFlags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allParagraphs = false end -- if any para specifically requested, don't keep all end end if filesOnly then allParagraphs = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberFlags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileArgs = options.fileargs and mw.text.trim(options.fileargs) if fileArgs == '' then fileArgs = nil end local leadStart = nil -- have we found some text yet? local t = "" -- the stripped down output text local fileText = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space -- Add named files local f = options.files if f and mw.ustring.match(f, "[^%d%s%-,]") then -- filename rather than number list f = mw.ustring.gsub(f, "^%s*File%s*:%s*", "", 1) f = mw.ustring.gsub(f, "^%s*Image%s*:%s*", "", 1) f = "[[File:" .. f .. "]]" f = modifyImage(f, "thumb") f = modifyImage(f, fileArgs) if checkImage(f) then fileText = fileText .. f .. "\n" end end repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} or {| Table |} if not leadStart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchAny(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if leadStart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not filesOnly and not startLine then t = t .. token end elseif matchAny(token, "^{{%s*", d.wantedBlockTemplates, "%s*%f[|}]") then t = t .. token -- keep wanted block templates elseif is(options.keepTables) and mw.ustring.sub(token, 1, 2) == '{|' then t = t .. token -- keep tables elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argImage(token) or {} if not images then local image = parseImage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkImage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = modifyImage(image, "thumb") image = modifyImage(image, fileArgs) fileText = fileText .. image end end end end else -- the next token in text is not a template token = parseImage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkImage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original image = modifyImage(image, fileArgs) fileText = fileText .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterEnd = mw.ustring.len(text) + 1 local blankPosition = mw.ustring.find(text, "\n%s*\n") or afterEnd -- position of next paragraph delimiter (or end of text) local endPosition = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterEnd, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterEnd, blankPosition) token = mw.ustring.sub(text, 1, endPosition-1) if blankPosition < afterEnd and blankPosition == endPosition then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankPosition) end local isHatnote = not(leadStart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadStart = leadStart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allParagraphs or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$") -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return fileText, text end local function cleanupText(text, options) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits if mw.ustring.find(text, "[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]") then -- avoid expensive search if possible text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text between onlyinclude sections text = mw.ustring.gsub(text, "^.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text before first onlyinclude section text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.*", "") -- remove text after last onlyinclude section end if not is(options.keepSubsections) then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if the lead is empty end if not is(options.keepRefs) then text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "%b{}", stripTemplate) -- remove unwanted templates such as references end text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImageMap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getSection(text, section, mainOnly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return nil end -- no such section local nextSection if mainOnly then nextSection = "\n==.*" -- Main part of section terminates at any level of header else nextSection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextSection, "") -- remove later sections with headings at this level or higher return content end -- Remove unmatched <tag> or </tag> tags local function fixTags(text, tag) local startCount = 0 for i in mw.ustring.gmatch(text, "<%s*" .. tag .. "%f[^%w_].->") do startCount = startCount + 1 end local endCount = 0 for i in mw.ustring.gmatch(text, "<%s*/" .. tag .. "%f[^%w_].->") do endCount = endCount + 1 end if startCount > endCount then -- more <tag> than </tag>: remove the last few <tag>s local i = 0 text = mw.ustring.gsub(text, "<%s*" .. tag .. "%f[^%w_].->", function(t) i = i + 1 if i > endCount then return "" else return nil end end) -- "end" here terminates the anonymous replacement function(t) passed to gsub elseif endCount > startCount then -- more </tag> than <tag>: remove the first few </tag>s text = mw.ustring.gsub(text, "<%s*/" .. tag .. "%f[^%w_].->", "", endCount - startCount) end return text end -- Main function returns a string value: text of the lead of a page local function main(pageNames, options) if not pageNames or #pageNames < 1 then return err("No page names given") end local pageName local text local pageCount = #pageNames local firstPage = pageNames[1] or "(nil)" -- save for error message, as it the name will be deleted local gotOptions local pageOptionsString local section -- read the page, or a random one if multiple pages were provided if pageCount > 1 then math.randomseed(os.time()) end while not text and pageCount > 0 do local pageNumber = 1 if pageCount > 1 then pageNumber = math.random(pageCount) end -- pick a random title pageName = pageNames[pageNumber] if pageName and pageName ~= "" then -- We have page or [[page]] or [[page|text]], possibly followed by |opt1|opt2... local pn pn, gotOptions, pageOptionsString = mw.ustring.match(pageName, "^%s*(%[%b[]%])%s*(|?)(.*)") if pn then pageName = mw.ustring.match(pn, "%[%[([^|%]]*)") -- turn [[page|text]] into page, discarding text else -- we have page or page|opt... pageName, gotOptions, pageOptionsString = mw.ustring.match(pageName, "%s*([^|]*[^|%s])%s*(|?)(.*)") end if pageName and pageName ~= "" then local pn pn, section = mw.ustring.match(pageName, "(.-)#(.*)") pageName = pn or pageName text, normalisedPageName = getContent(pageName) if is(options.fragment) then local frame = mw.getCurrentFrame() text = frame:callParserFunction('#lst', normalisedPageName, options.fragment) end if not normalisedPageName then return err("No title for page name " .. pageName) else pageName = normalisedPageName end if text and options.nostubs then local isStub = mw.ustring.find(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}") if isStub then text = nil end end if not section then section = mw.ustring.match(pageName, ".-#(.*)") -- parse redirect to Page#Section end if text and section and section ~= "" then text = getSection(text, section) end end end if not text then table.remove(pageNames, pageNumber) end -- this one didn't work; try another pageCount = pageCount - 1 -- ensure that we exit the loop after at most #pageNames iterations end if not text then return err("Cannot read a valid page: first name is " .. firstPage) end text = cleanupText(text, options) local pageOptions = {} -- pageOptions (even if value is "") have priority over global options for k, v in pairs(options) do pageOptions[k] = v end if gotOptions and gotOptions ~= "" then for _, t in pairs(mw.text.split(pageOptionsString, "|")) do local k, v = mw.ustring.match(t, "%s*([^=]-)%s*=(.-)%s*$") pageOptions[k] = v end pageOptions.paraflags = numberFlags(pageOptions["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} pageOptions.fileflags = numberFlags(pageOptions["files"] or "") -- parse file numbers if pageOptions.more and pageOptions.more == "" then pageOptions.more = "Read more..." end -- more= is short for this default text end local fileText fileText, text = parse(text, pageOptions) -- replace the bold title or synonym near the start of the article by a wikilink to the article local lang = mw.language.getContentLanguage() local pos = mw.ustring.find(text, "'''" .. lang:ucfirst(pageName) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(pageName) .. "'''", 1, true) -- plain search: special characters in pageName represent themselves if pos then local len = mw.ustring.len(pageName) text = mw.ustring.sub(text, 1, pos + 2) .. "[[" .. mw.ustring.sub(text, pos + 3, pos + len + 2) .. "]]" .. mw.ustring.sub(text, pos + len + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "()'''(.-'*)'''", function(a, b) if a < 100 and not mw.ustring.find(b, "%[") then ---if early in article and not wikilinked return "'''[[" .. pageName .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[pageName|Foo]]''' else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end -- remove '''bold text''' if requested if is(pageOptions.nobold) then text = mw.ustring.gsub(text, "'''", "") end text = fileText .. text -- Seek and destroy unterminated templates and wikilinks repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27") -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2") -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t repeat -- do similar for [[wikilink]]s local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27") until text == t text = text.gsub(text, "([{}%[%]])%1[^\27].*", "") -- remove unmatched {{, }}, [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([{}%[%]])%1$", "") -- remove unmatched {{, }}, [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", "") -- unhide matched pairs: E{E{ → {{, ]E]E → ]], etc. -- Ensure div tags match text = fixTags(text, "div") if pageOptions.more then text = text .. " '''[[" .. pageName .. "|" .. pageOptions.more .. "]]'''" end -- wikilink to article for more info if pageOptions.list and not pageOptions.showall then -- add a collapsed list of pages which might appear local listtext = pageOptions.list if listtext == "" then listtext = "Other articles" end text = text .. "{{collapse top|title={{resize|85%|" ..listtext .. "}}|bg=fff}}{{hlist" for _, p in pairs(pageNames) do if mw.ustring.match(p, "%S") then text = text .. "|[[" .. mw.text.trim(p) .. "]]" end end text = text .. "}}\n{{collapse bottom}}" end return text end -- Shared template invocation code for lead and random functions local function invoke(frame, template) -- args = { 1,2,... = page names, paragraphs = list e.g. "1,3-5", files = list, more = text} local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template errors = args["errors"] -- set the module level boolean used in local function err local articleCount = #args -- must be 1 except with selected=Foo and Foo=Somepage if articleCount < 1 and not (template == "selected" and args[template] and args[args[template]]) then return err("No articles provided") end local pageNames = {} if template == "lead" then pageNames = { args[1] } elseif template == "linked" or template == "listitem" then -- Read named page and find its wikilinks local page = args[1] local text, title = getContent(page) if not title then return err("No title for page name " .. page) elseif not text then return err("No content for page name " .. page) end if args["section"] then -- check relevant section only text = getSection(text, args["section"], args["sectiononly"]) if not text then return err("No section " .. args["section"] .. " in page " .. page) end end -- replace annotated links with real links text = mw.ustring.gsub(text, "{{%s*[Aa]nnotated[ _]link%s*|%s*(.-)%s*}}", "[[%1]]") if template == "linked" then for p in mw.ustring.gmatch(text, "%[%[%s*([^%]|\n]*)") do table.insert(pageNames, p) end else -- listitem: first wikilink on a line beginning *, :#, etc. except in "See also" or later section text = mw.ustring.gsub(text, "\n== *See also.*", "") for p in mw.ustring.gmatch(text, "\n:*[%*#][^\n]-%[%[%s*([^%]|\n]*)") do table.insert(pageNames, p) end end elseif template == "random" then -- accept any number of page names. If more than one, we'll pick one randomly for i, p in pairs(args) do if p and type(i) == 'number' then table.insert(pageNames, p) end end elseif template == "selected" then local articleKey = args[template] if tonumber(articleKey) then -- normalise article number into the range 1..#args articleKey = articleKey % articleCount if articleKey == 0 then articleKey = articleCount end end pageNames = { args[articleKey] } end local options = args -- pick up miscellaneous options: more, errors, fileargs options.paraflags = numberFlags(args["paragraphs"] or "") -- parse paragraphs, e.g. "1,3-5" → {"1","3-5"} options.fileflags = numberFlags(args["files"] or "") -- parse file numbers if options.more and options.more == "" then options.more = "Read more..." end -- more= is short for this default text local text = "" if options.showall then local separator = "" for _, p in pairs(pageNames) do local t = main({ p }, options) if t ~= "" then text = text .. separator .. t separator = options.showall if separator == "" then separator = "{{clear}}{{hr}}" end end end else text = main(pageNames, options) end if text == "" and d.brokenCategory and d.brokenCategory ~= "" and mw.title.getCurrentTitle().isContentPage then return "[[Category:" .. d.brokenCategory .. "]]" else return frame:preprocess(text) end end -- Replicate {{Excerpt}} entirely in Lua for reduced Post-expand include size local function excerpt(frame) local args = {} -- args[k] = frame.args[k] or frame:getParent().args[k] for all k in either (numeric or not) for k, v in pairs(frame:getParent().args) do args[k] = v end for k, v in pairs(frame.args) do args[k] = v end -- args from a Lua call have priority over parent args from template local tag = is(args.tag) and args.tag or 'div' local article = is(args.article) and args.article or args[1] or '{{{1}}}' local section = is(args.section) and args.section or args[2] local output = {} output[1] = frame:extensionTag{ name = 'templatestyles', args = {src='Excerpt/styles.css'} } output[2] = '<' .. tag .. ' class="excerpt-block">' output[3] = is(args.indicator) and ('<' .. tag .. ' class="excerpt-indicator">') or '' if is(args.nohat) then output[4] = '' else local hatnote = {} hatnote[1] = 'This' .. (is(args.indicator) and '' or ' section') .. ' is an excerpt from ' hatnote[2] = '[[' hatnote[3] = article .. (is(section) and ('#' .. frame:callParserFunction( 'urlencode', section, 'WIKI' )) or '') hatnote[4] = '|' hatnote[5] = article .. (is(section) and (frame:callParserFunction( '#tag:nowiki', ' § ' ) .. section) or '') hatnote[6] = ']]' hatnote[7] = "''" .. '<span class="mw-editsection-like plainlinks"><span>[ </span>[' local title = mw.title.new(article) or mw.title.getCurrentTitle() hatnote[8] = title:fullUrl('action=edit') .. ' edit' hatnote[9] = ']<span> ]</span></span>' .. "''" output[4] = require('Module:Hatnote')._hatnote(table.concat(hatnote), {selfref=true}) or err("Error generating hatnote") end output[5] = '<' .. tag .. ' class="excerpt">\n' if article ~= '{{{1}}}' then local options = args -- turn template arguments into module options options.paraflags = args.paragraphs options.fileflags = args.files or 1 options.nobold = 1 options.fragment = args.fragment options.keepTables = args.tables or 1 options.keepRefs = args.references or 1 options.keepSubsections = args.subsections local pageNames = { (article .. '#' .. (section or '')) } local text = main(pageNames, options) if text == "" and d.brokenCategory and d.brokenCategory ~= "" and mw.title.getCurrentTitle().isContentPage then output[6] = "[[Category:" .. d.brokenCategory .. "]]" else output[6] = frame:preprocess(text) or err("Error processing text") end else output[6] = err("No article provided") end output[7] = '</' .. tag .. '>' output[8] = is(args.indicator) and ('</' .. tag .. '>') or '' output[9] = '</' .. tag .. '>' output[10] = mw.title.getCurrentTitle().isContentPage and '[[Category:Articles with excerpts]]' or '' return table.concat(output) end -- Entry points for template callers using #invoke: function p.lead(frame) return invoke(frame, "lead") end -- {{Transclude lead excerpt}} reads the first and only article function p.linked(frame) return invoke(frame, "linked") end -- {{Transclude linked excerpt}} reads a randomly selected article linked from the given page function p.listitem(frame) return invoke(frame, "listitem") end -- {{Transclude list item excerpt}} reads a randomly selected article listed on the given page function p.random(frame) return invoke(frame, "random") end -- {{Transclude random excerpt}} reads any article (default for invoke with one argument) function p.selected(frame) return invoke(frame, "selected") end -- {{Transclude selected excerpt}} reads the article whose key is in the selected= parameter function p.excerpt(frame) return excerpt(frame) end -- {{Excerpt}} transcludes part of an article into another article -- Entry points for other Lua modules function p.getContent(page, frame) return getContent(page, frame) end function p.getsection(text, section) return getSection(text, section) end function p.parse(text, options, filesOnly) return parse(text, options, filesOnly) end function p.argimage(text) return argImage(text) end function p.checkimage(image) return checkImage(image) end function p.parseimage(text, start) return parseImage(text, start) end function p.cleanupText(text, options) return cleanupText(text, options) end function p.main(pageNames, options) return main(pageNames, options) end function p.numberflags(str) return numberFlags(str) end return p sievx9lgslmbhs1cy4j61zsp5we2s7m 797332 797331 2020-06-01T12:56:35Z en>Sophivorus 0 Update to new version 797332 Scribunto text/plain -- Get localized data local d = require("Module:Excerpt/i18n") local p = {} -- Helper function to test for truthy and falsy values local function is(value) if not value or value == "" or value == "0" or value == "false" or value == "no" then return false end return true end -- Error handling function -- Throws a Lua error or returns an empty string if error reporting is disabled errors = true -- show errors by default local function luaError(message, value) if not is(errors) then return "" end -- error reporting is disabled message = d.errors[message] or message or "" message = mw.ustring.format(message, value) error(message, 2) end -- Error handling function -- Returns a wiki friendly error or an empty string if error reporting is disabled local function wikiError(message, value) if not is(errors) then return "" end -- error reporting is disabled message = d.errors[message] or message or "" message = mw.ustring.format(message, value) message = d.errors.prefix .. message if mw.title.getCurrentTitle().isContentPage then local errorsCategory = mw.title.new(d.errorsCategory, 'Category') if errorsCategory then message = message .. '[[' .. errorsCategory.prefixedText .. ']]' end end message = mw.html.create('div'):addClass('error'):wikitext(message) return message end -- Helper function to match from a list regular expressions -- Like so: match pre..list[1]..post or pre..list[2]..post or ... local function matchAny(text, pre, list, post, init) local match = {} for i = 1, #list do match = { mw.ustring.match(text, pre .. list[i] .. post, init) } if match[1] then return unpack(match) end end return nil end -- Helper function to convert imagemaps into standard images local function convertImageMap(imagemap) local image = matchAny(imagemap, "[>\n]%s*", d.fileNamespaces, "[^\n]*") if image then return "<!--imagemap-->[[" .. mw.ustring.gsub(image, "[>\n]%s*", "", 1) .. "]]" else return "" -- remove entire block if image can't be extracted end end -- Helper function to convert a comma-separated list of numbers or min-max ranges into a list of booleans -- For example: "1,3-5" to {1=true,2=false,3=true,4=true,5=true} local function numberFlags(str) if not str then return {} end local flags = {} local ranges = mw.text.split(str, ",") -- parse ranges: "1,3-5" to {"1","3-5"} for _, r in pairs(ranges) do local min, max = mw.ustring.match(r, "^%s*(%d+)%s*%-%s*(%d+)%s*$") -- "3-5" to min=3 max=5 if not max then min, max = mw.ustring.match(r, "^%s*((%d+))%s*$") end -- "1" to min=1 max=1 if max then for p = min, max do flags[p] = true end end end return flags end -- Helper function to convert template arguments into an array of arguments fit for get() local function parseArgs(frame) local args = {} for key, value in pairs(frame:getParent().args) do args[key] = value end for key, value in pairs(frame.args) do args[key] = value end -- args from a Lua call have priority over parent args from template args.paraflags = numberFlags(args["paragraphs"] or "") -- parse paragraphs: "1,3-5" to {"1","3-5"} args.fileflags = numberFlags(args["files"] or "") -- parse file numbers return args end -- Helper function to remove unwanted templates and pseudo-templates such as #tag:ref and DEFAULTSORT local function stripTemplate(t) -- If template is unwanted then return "" (gsub will replace by nothing), else return nil (gsub will keep existing string) if matchAny(t, "^{{%s*", d.unwantedInlineTemplates, "%s*%f[|}]") then return "" end -- If template is wanted but produces an unwanted reference then return the string with |shortref or |ref removed local noRef = mw.ustring.gsub(t, "|%s*shortref%s*%f[|}]", "") noRef = mw.ustring.gsub(noRef, "|%s*ref%s*%f[|}]", "") -- If a wanted template has unwanted nested templates, purge them too noRef = mw.ustring.sub(noRef, 1, 2) .. mw.ustring.gsub(mw.ustring.sub(noRef, 3), "%b{}", stripTemplate) -- Replace {{audio}} by its text parameter: {{Audio|Foo.ogg|Bar}} → Bar noRef = mw.ustring.gsub(noRef, "^{{%s*[Aa]udio.-|.-|(.-)%f[|}].*", "%1") -- Replace {{Nihongo foot}} by its text parameter: {{Nihongo foot|English|英語|eigo}} → English noRef = mw.ustring.gsub(noRef, "^{{%s*[Nn]ihongo[ _]+foot%s*|(.-)%f[|}].*", "%1") if noRef ~= t then return noRef end return nil -- not an unwanted template: keep end -- Get a page's content, following redirects -- Also returns the page name, or the target page name if a redirect was followed, or false if no page found -- For file pages, returns the content of the file description page local function getContent(page) local title = mw.title.new(page) if not title then return false, false end local target = title.redirectTarget if target then title = target end return title:getContent(), title.prefixedText end -- Get the tables only local function getTables(text, options) local tables = {} for candidate in mw.ustring.gmatch(text, "%b{}") do if mw.ustring.sub(candidate, 1, 2) == '{|' then table.insert(tables, candidate) end end return table.concat(tables, '\n') end -- Get the lists only local function getLists(text, options) local lists = {} for list in mw.ustring.gmatch(text, "\n[*#][^\n]+") do table.insert(lists, list) end return table.concat(lists, '\n') end -- Check image for suitability local function checkImage(image) local page = matchAny(image, "", d.fileNamespaces, "%s*:[^|%]]*") -- match File:(name) or Image:(name) if not page then return false end -- Limit to image types: .gif, .jpg, .jpeg, .png, .svg, .tiff, .xcf (exclude .ogg, audio, etc.) local fileTypes = {"[Gg][Ii][Ff]", "[Jj][Pp][Ee]?[Gg]", "[Pp][Nn][Gg]", "[Ss][Vv][Gg]", "[Tt][Ii][Ff][Ff]", "[Xx][Cc][Ff]"} if not matchAny(page, "%.", fileTypes, "%s*$") then return false end -- Check the local wiki local fileDescription, fileTitle = getContent(page) -- get file description and title after following any redirect if not fileTitle or fileTitle == "" then return false end -- the image doesn't exist -- Check Commons if not fileDescription or fileDescription == "" then local frame = mw.getCurrentFrame() fileDescription = frame:preprocess("{{" .. fileTitle .. "}}") end -- Filter non-free images if not fileDescription or fileDescription == "" or mw.ustring.match(fileDescription, "[Nn]on%-free") then return false end return true end -- Attempt to parse [[File:...]] or [[Image:...]], either anywhere (start=false) or at the start only (start=true) local function parseImage(text, start) local startre = "" if start then startre = "^" end -- a true flag restricts search to start of string local image = matchAny(text, startre .. "%[%[%s*", d.fileNamespaces, "%s*:.*") -- [[File: or [[Image: ... if image then image = mw.ustring.match(image, "%b[]%s*") -- matching [[...]] to handle wikilinks nested in caption end return image end -- Parse a caption, which ends at a | (end of parameter) or } (end of infobox) but may contain nested [..] and {..} local function parseCaption(caption) if not caption then return nil end local length = mw.ustring.len(caption) local position = 1 while position <= length do local linkStart, linkEnd = mw.ustring.find(caption, "%b[]", position) linkStart = linkStart or length + 1 -- avoid comparison with nil when no link local templateStart, templateEnd = mw.ustring.find(caption, "%b{}", position) templateStart = templateStart or length + 1 -- avoid comparison with nil when no template local argEnd = mw.ustring.find(caption, "[|}]", position) or length + 1 if linkStart < templateStart and linkStart < argEnd then position = linkEnd + 1 -- skip wikilink elseif templateStart < argEnd then position = templateEnd + 1 -- skip template else -- argument ends before the next wikilink or template return mw.ustring.sub(caption, 1, argEnd - 1) end end return caption -- No terminator found: return entire caption end -- Attempt to construct a [[File:...]] block from {{infobox ... |image= ...}} local function argImage(text) local token = nil local hasNamedArgs = mw.ustring.find(text, "|") and mw.ustring.find(text, "=") if not hasNamedArgs then return nil end -- filter out any template that obviously doesn't contain an image -- ensure image map is captured text = mw.ustring.gsub(text, '<!%-%-imagemap%-%->', '|imagemap=') -- find all images local hasImages = false local images = {} local captureFrom = 1 while captureFrom < mw.ustring.len(text) do local argname, position, image = mw.ustring.match(text, "|%s*([^=|]-[Ii][Mm][Aa][Gg][Ee][^=|]-)%s*=%s*()(.*)", captureFrom) if image then -- ImageCaption=, image_size=, image_upright=, etc. do not introduce an image local lcArgName = mw.ustring.lower(argname) if mw.ustring.find(lcArgName, "caption") or mw.ustring.find(lcArgName, "size") or mw.ustring.find(lcArgName, "upright") then image = nil end end if image then hasImages = true images[position] = image captureFrom = position else captureFrom = mw.ustring.len(text) end end captureFrom = 1 while captureFrom < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|]-[Pp][Hh][Oo][Tt][Oo][^=|]-%s*=%s*()(.*)", captureFrom) if image then hasImages = true images[position] = image captureFrom = position else captureFrom = mw.ustring.len(text) end end captureFrom = 1 while captureFrom < mw.ustring.len(text) do local position, image = mw.ustring.match(text, "|%s*[^=|{}]-%s*=%s*()%[?%[?([^|{}]*%.%a%a%a%a?)%s*%f[|}]", captureFrom) if image then hasImages = true if not images[position] then images[position] = image end captureFrom = position else captureFrom = mw.ustring.len(text) end end if not hasImages then return nil end -- find all captions local captions = {} captureFrom = 1 while captureFrom < mw.ustring.len(text) do local position, caption = matchAny(text, "|%s*", d.captionParams, "%s*=%s*()([^\n]+)", captureFrom) if caption then -- extend caption to parse "| caption = Foo {{Template\n on\n multiple lines}} Bar\n" local bracedCaption = mw.ustring.match(text, "^[^\n]-%b{}[^\n]+", position) if bracedCaption and bracedCaption ~= "" then caption = bracedCaption end caption = mw.text.trim(caption) local captionStart = mw.ustring.sub(caption, 1, 1) if captionStart == '|' or captionStart == '}' then caption = nil end end if caption then -- find nearest image, and use same index for captions table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not captions[i] then captions[i] = parseCaption(caption) end end end captureFrom = position else captureFrom = mw.ustring.len(text) end end -- find all alt text local altTexts = {} for position, altText in mw.ustring.gmatch(text, "|%s*[Aa][Ll][Tt]%s*=%s*()([^\n]*)") do if altText then -- altText is terminated by }} or |, but first skip any matched [[...]] and {{...}} local lookFrom = math.max( -- find position after whichever comes last: start of string, end of last ]] or end of last }} mw.ustring.match(altText, ".*{%b{}}()") or 1, -- if multiple {{...}}, .* consumes all but one, leaving the last for %b mw.ustring.match(altText, ".*%[%b[]%]()") or 1) local length = mw.ustring.len(altText) local afterText = math.min( -- find position after whichever comes first: end of string, }} or | mw.ustring.match(altText, "()}}", lookFrom) or length+1, mw.ustring.match(altText, "()|", lookFrom) or length+1) altText = mw.ustring.sub(altText, 1, afterText-1) -- chop off |... or }}... which is not part of [[...]] or {{...}} altText = mw.text.trim(altText) local altTextStart = mw.ustring.sub(altText, 1, 1) if altTextStart == '|' or altTextStart == '}' then altText = nil end end if altText then -- find nearest image, and use same index for altTexts table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not altTexts[i] then altTexts[i] = altText end end end end end -- find all image sizes local imageSizes = {} for position, imageSizeMatch in mw.ustring.gmatch(text, "|%s*[Ii][Mm][Aa][Gg][Ee][ _]?[Ss][Ii][Zz][Ee]%s*=%s*()([^}|\n]*)") do local imageSize = mw.ustring.match(imageSizeMatch, "=%s*([^}|\n]*)") if imageSize then imageSize = mw.text.trim(imageSize ) local imageSizeStart = mw.ustring.sub(imageSize, 1, 1) if imageSizeStart == '|' or imageSizeStart == '}' then imageSize = nil end end if imageSize then -- find nearest image, and use same index for imageSizes table local i = position while i > 0 and not images[i] do i = i - 1 if images[i] then if not imageSizes[i] then imageSizes[i] = imageSize end end end end end -- sort the keys of the images table (in a table sequence), so that images can be iterated over in order local keys = {} for key, val in pairs(images) do table.insert(keys, key) end table.sort(keys) -- add in relevant optional parameters for each image: caption, alt text and image size local imageTokens = {} for _, index in ipairs(keys) do local image = images[index] local token = parseImage(image, true) -- look for image=[[File:...]] etc. if not token then image = mw.ustring.match(image, "^[^}|\n]*") -- remove later arguments token = "[[" -- Add File: unless name already begins File: or Image: if not matchAny(image, "^", d.fileNamespaces, "%s*:") then token = token .. "File:" end token = token .. image local caption = captions[index] if caption and mw.ustring.match(caption, "%S") then token = token .. "|" .. caption end local alt = altTexts[index] if alt then token = token .. "|alt=" .. alt end local image_size = imageSizes[index] if image_size and mw.ustring.match(image_size, "%S") then token = token .. "|" .. image_size end token = token .. "]]" end token = mw.ustring.gsub(token, "\n","") .. "\n" table.insert(imageTokens, token) end return imageTokens end local function modifyImage(image, fileArgs) if fileArgs then for _, filearg in pairs(mw.text.split(fileArgs, "|")) do -- handle fileArgs=left|border etc. local fa = mw.ustring.gsub(filearg, "=.*", "") -- "upright=0.75" → "upright" local group = {fa} -- group of "border" is ["border"]... for _, g in pairs(d.imageParams) do for _, a in pairs(g) do if fa == a then group = g end -- ...but group of "left" is ["right", "left", "center", "none"] end end for _, a in pairs(group) do image = mw.ustring.gsub(image, "|%s*" .. a .. "%f[%A]%s*=[^|%]]*", "") -- remove "|upright=0.75" etc. image = mw.ustring.gsub(image, "|%s*" .. a .. "%s*([|%]])", "%1") -- replace "|left|" by "|" etc. end image = mw.ustring.gsub(image, "([|%]])", "|" .. filearg .. "%1", 1) -- replace "|" by "|left|" etc. end end return image end -- a basic parser to trim down extracted wikitext -- @param text : Wikitext to be processed -- @param options : A table of options... -- options.paraflags : Which number paragraphs to keep, as either a string (e.g. '1,3-5') or a table (e.g. {1=true,2=false,3=true,4=true,5=true}. If not present, all paragraphs will be kept. -- options.fileflags : table of which files to keep, as either a string (e.g. '1,3-5') or a table (e.g. {1=true,2=false,3=true,4=true,5=true} -- options.fileargs : args for the [[File:]] syntax, such as 'left' -- options.filesOnly : only return the files and not the prose local function parse(text, options) local allParagraphs = true -- keep all paragraphs? if options.paraflags then if type(options.paraflags) ~= "table" then options.paraflags = numberFlags(options.paraflags) end for _, v in pairs(options.paraflags) do if v then allParagraphs = false end -- if any para specifically requested, don't keep all end end if is(options.filesOnly) then allParagraphs = false options.paraflags = {} end local maxfile = 0 -- for efficiency, stop checking images after this many have been found if options.fileflags then if type(options.fileflags) ~= "table" then options.fileflags = numberFlags(options.fileflags) end for k, v in pairs(options.fileflags) do if v and k > maxfile then maxfile = k end -- set maxfile = highest key in fileflags end end local fileArgs = options.fileargs and mw.text.trim(options.fileargs) if fileArgs == '' then fileArgs = nil end local leadStart = nil -- have we found some text yet? local t = "" -- the stripped down output text local fileText = "" -- output text with concatenated [[File:Foo|...]]\n entries local files = 0 -- how many images so far local paras = 0 -- how many paragraphs so far local startLine = true -- at the start of a line (no non-spaces found since last \n)? text = mw.ustring.gsub(text,"^%s*","") -- remove initial white space -- Add named files local f = options.files if f and mw.ustring.match(f, "[^%d%s%-,]") then -- filename rather than number list f = mw.ustring.gsub(f, "^%s*File%s*:%s*", "", 1) f = mw.ustring.gsub(f, "^%s*Image%s*:%s*", "", 1) f = "[[File:" .. f .. "]]" f = modifyImage(f, "thumb") f = modifyImage(f, fileArgs) if checkImage(f) then fileText = fileText .. f .. "\n" end end repeat -- loop around parsing a template, image or paragraph local token = mw.ustring.match(text, "^%b{}%s*") or false -- {{Template}} or {| Table |} if not leadStart and not token then token = mw.ustring.match(text, "^%b<>%s*%b{}%s*") end -- allow <tag>{{template}} before lead has started local line = mw.ustring.match(text, "[^\n]*") if token and line and mw.ustring.len(token) < mw.ustring.len(line) then -- template is followed by text (but it may just be other templates) line = mw.ustring.gsub(line, "%b{}", "") -- remove all templates from this line line = mw.ustring.gsub(line, "%b<>", "") -- remove all HTML tags from this line -- if anything is left, other than an incomplete further template or an image, keep the template: it counts as part of the line if mw.ustring.find(line, "%S") and not matchAny(line, "^%s*", { "{{", "%[%[%s*[Ff]ile:", "%[%[%s*[Ii]mage:" }, "") then token = nil end end if token then -- found a template which is not the prefix to a line of text if is(options.keepTables) and mw.ustring.sub(token, 1, 2) == '{|' then t = t .. token -- keep tables elseif mw.ustring.sub(token, 1, 3) == '{{#' then t = t .. token -- keep parser functions elseif leadStart then -- lead has already started, so keep the template within the text, unless it's a whole line (navbox etc.) if not is(options.filesOnly) and not startLine then t = t .. token end elseif matchAny(token, "^{{%s*", d.wantedBlockTemplates, "%s*%f[|}]") then t = t .. token -- keep wanted block templates elseif files < maxfile then -- discard template, but if we are still collecting images... local images = argImage(token) or {} if not images then local image = parseImage(token, false) -- look for embedded [[File:...]], |image=, etc. if image then table.insert(images, image) end end for _, image in ipairs(images) do if files < maxfile and checkImage(image) then -- if image is found and qualifies (not a sound file, non-free, etc.) files = files + 1 -- count the file, whether displaying it or not if options.fileflags and options.fileflags[files] then -- if displaying this image image = modifyImage(image, "thumb") image = modifyImage(image, fileArgs) fileText = fileText .. image end end end end else -- the next token in text is not a template token = parseImage(text, true) if token then -- the next token in text looks like an image if files < maxfile and checkImage(token) then -- if more images are wanted and this is a wanted image files = files + 1 if options.fileflags and options.fileflags[files] then local image = token -- copy token for manipulation by adding |right etc. without changing the original image = modifyImage(image, fileArgs) fileText = fileText .. image end end else -- got a paragraph, which ends at a file, image, blank line or end of text local afterEnd = mw.ustring.len(text) + 1 local blankPosition = mw.ustring.find(text, "\n%s*\n") or afterEnd -- position of next paragraph delimiter (or end of text) local endPosition = math.min( -- find position of whichever comes first: [[File:, [[Image: or paragraph delimiter mw.ustring.find(text, "%[%[%s*[Ff]ile%s*:") or afterEnd, mw.ustring.find(text, "%[%[%s*[Ii]mage%s*:") or afterEnd, blankPosition) token = mw.ustring.sub(text, 1, endPosition-1) if blankPosition < afterEnd and blankPosition == endPosition then -- paragraph ends with a blank line token = token .. mw.ustring.match(text, "\n%s*\n", blankPosition) end local isHatnote = not(leadStart) and mw.ustring.sub(token, 1, 1) == ':' if not isHatnote then leadStart = leadStart or mw.ustring.len(t) + 1 -- we got a paragraph, so mark the start of the lead section paras = paras + 1 if allParagraphs or (options.paraflags and options.paraflags[paras]) then t = t .. token end -- add if this paragraph wanted end end -- of "else got a paragraph" end -- of "else not a template" if token then text = mw.ustring.sub(text, mw.ustring.len(token)+1) end -- remove parsed token from remaining text startLine = mw.ustring.find(token, "\n%s*$") -- will the next token be the first non-space on a line? until not text or text == "" or not token or token == "" -- loop until all text parsed text = mw.ustring.gsub(t, "\n+$", "") -- remove trailing line feeds, so "{{Transclude text excerpt|Foo}} more" flows on one line return fileText .. text end local function cleanupText(text, options) text = mw.ustring.gsub(text, "<!%-%-.-%-%->","") -- remove HTML comments text = mw.ustring.gsub(text, "<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove noinclude bits if mw.ustring.find(text, "[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]") then -- avoid expensive search if possible text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text between onlyinclude sections text = mw.ustring.gsub(text, "^.-<[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>", "") -- remove text before first onlyinclude section text = mw.ustring.gsub(text, "</[Oo][Nn][Ll][Yy][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.*", "") -- remove text after last onlyinclude section end if not is(options.keepSubsections) then text = mw.ustring.gsub(text, "\n==.*","") -- remove first ==Heading== and everything after it text = mw.ustring.gsub(text, "^==.*","") -- ...even if the lead is empty end if not is(options.keepRefs) then text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]-/%s*>", "") -- remove refs cited elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff].->.-<%s*/%s*[Rr][Ee][Ff]%s*>", "") -- remove refs text = mw.ustring.gsub(text, "%b{}", stripTemplate) -- remove unwanted templates such as references end text = mw.ustring.gsub(text, "<%s*[Ss][Cc][Oo][Rr][Ee].->.-<%s*/%s*[Ss][Cc][Oo][Rr][Ee]%s*>", "") -- remove musical scores text = mw.ustring.gsub(text, "<%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp].->.-<%s*/%s*[Ii][Mm][Aa][Gg][Ee][Mm][Aa][Pp]%s*>", convertImageMap) -- convert imagemaps into standard images text = mw.ustring.gsub(text, "%s*{{%s*[Tt][Oo][Cc].-}}", "") -- remove most common tables of contents text = mw.ustring.gsub(text, "%s*__[A-Z]*TOC__", "") -- remove TOC behavior switches text = mw.ustring.gsub(text, "\n%s*{{%s*[Pp]p%-.-}}", "\n") -- remove protection templates text = mw.ustring.gsub(text, "%s*{{[^{|}]*[Ss]idebar%s*}}", "") -- remove most sidebars text = mw.ustring.gsub(text, "%s*{{[^{|}]*%-[Ss]tub%s*}}", "") -- remove most stub templates text = mw.ustring.gsub(text, "%s*%[%[%s*:?[Cc]ategory:.-%]%]", "") -- remove categories text = mw.ustring.gsub(text, "^:[^\n]+\n","") -- remove DIY hatnote indented with a colon return text end -- Parse a ==Section== from a page local function getSection(text, section, mainOnly) local escapedSection = mw.ustring.gsub(mw.uri.decode(section), "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") -- %26 → & etc, then ^ → %^ etc. local level, content = mw.ustring.match(text .. "\n", "\n(==+)%s*" .. escapedSection .. "%s*==.-\n(.*)") if not content then return luaError("sectionNotFound", section) end local nextSection if mainOnly then nextSection = "\n==.*" -- Main part of section terminates at any level of header else nextSection = "\n==" .. mw.ustring.rep("=?", #level - 2) .. "[^=].*" -- "===" → "\n===?[^=].*", matching "==" or "===" but not "====" end content = mw.ustring.gsub(content, nextSection, "") -- remove later sections with headings at this level or higher if mw.ustring.match(content, "^%s*$") then return luaError("sectionEmpty", section) end return content end -- Parse a <section begin="Name of the fragment"> -- @todo Implement custom parsing of fragments rather than relying on #lst local function getFragment(page, fragment) local frame = mw.getCurrentFrame() local text = frame:callParserFunction('#lst', page, fragment) if mw.ustring.match(text, "^%s*$") then return luaError("fragmentEmpty", fragment) end return text end -- Remove unmatched <tag> or </tag> tags local function fixTags(text, tag) local startCount = 0 for i in mw.ustring.gmatch(text, "<%s*" .. tag .. "%f[^%w_].->") do startCount = startCount + 1 end local endCount = 0 for i in mw.ustring.gmatch(text, "<%s*/" .. tag .. "%f[^%w_].->") do endCount = endCount + 1 end if startCount > endCount then -- more <tag> than </tag>: remove the last few <tag>s local i = 0 text = mw.ustring.gsub(text, "<%s*" .. tag .. "%f[^%w_].->", function(t) i = i + 1 if i > endCount then return "" else return nil end end) -- "end" here terminates the anonymous replacement function(t) passed to gsub elseif endCount > startCount then -- more </tag> than <tag>: remove the first few </tag>s text = mw.ustring.gsub(text, "<%s*/" .. tag .. "%f[^%w_].->", "", endCount - startCount) end return text end local function fixTemplates(text) repeat -- hide matched {{template}}s including nested templates local t = text text = mw.ustring.gsub(text, "{(%b{})}", "\27{\27%1\27}\27") -- {{sometemplate}} → E{Esometemplate}E}E where E represents escape text = mw.ustring.gsub(text, "(< *math[^>]*>[^<]-)}}(.-< */math *>)", "%1}\27}\27%2") -- <math>\{sqrt\{hat{x}}</math> → <math>\{sqrt\{hat{x}E}E</math> until text == t text = text.gsub(text, "([{}])%1[^\27].*", "") -- remove unmatched {{, }} and everything thereafter, avoiding }E}E etc. text = text.gsub(text, "([{}])%1$", "") -- remove unmatched {{, }} at end of text text = mw.ustring.gsub(text, "\27", "") -- unhide matched pairs: E{E{ → {{, etc. return text end local function fixLinks(text) repeat -- hide matched [[wikilink]]s including nested links like [[File:Example.jpg|Some [[nested]] link.]] local t = text text = mw.ustring.gsub(text, "%[(%b[])%]", "\27[\27%1\27]\27") until text == t text = text.gsub(text, "([%[%]])%1[^\27].*", "") -- remove unmatched [[ or ]] and everything thereafter, avoiding ]E]E etc. text = text.gsub(text, "([%[%]])%1$", "") -- remove unmatched [[ or ]] at end of text text = mw.ustring.gsub(text, "\27", "") -- unhide matched pairs: ]E]E → ]], etc. return text end -- Replace the first call to each reference defined outside of the text for the full reference, to prevent undefined references -- Then prefix the page title to the reference names to prevent conflicts -- that is, replace <ref name="Foo"> for <ref name="Title of the article Foo"> -- and also <ref name="Foo" /> for <ref name="Title of the article Foo" /> -- also remove reference groups: <ref name="Foo" group="Bar"> for <ref name="Title of the article Foo"> -- and <ref group="Bar"> for <ref> -- @todo The current regex may fail in cases with both kinds of quotes, like <ref name="Darwin's book"> local function fixRefs(text, page, full) if not full then full = getContent(page) end local refNames = {} local refName local refBody local position = 1 while position < mw.ustring.len(text) do refName, position = mw.ustring.match(text, "<%s*[Rr][Ee][Ff][^>]*name%s*=%s*[\"']?([^\"'>]+)[\"']?[^>]*/%s*>()", position) if refName then refName = mw.text.trim(refName) if not refNames[refName] then -- make sure we process each ref name only once table.insert(refNames, refName) refName = mw.ustring.gsub(refName, "[%^%$%(%)%.%[%]%*%+%-%?%%]", "%%%0") -- escape special characters refBody = mw.ustring.match(text, "<%s*[Rr][Ee][Ff][^>]*name%s*=%s*[\"']?%s*" .. refName .. "%s*[\"']?[^>/]*>.-<%s*/%s*[Rr][Ee][Ff]%s*>") if not refBody then -- the ref body is not in the excerpt refBody = mw.ustring.match(full, "<%s*[Rr][Ee][Ff][^>]*name%s*=%s*[\"']?%s*" .. refName .. "%s*[\"']?[^/>]*>.-<%s*/%s*[Rr][Ee][Ff]%s*>") if refBody then -- the ref body was found elsewhere text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]*name%s*=%s*[\"']?%s*" .. refName .. "%s*[\"']?[^>]*/?%s*>", refBody, 1) end end end else position = mw.ustring.len(text) end end text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]*name%s*=%s*[\"']?([^\"'>/]+)[\"']?[^>/]*(/?)%s*>", '<ref name="' .. page .. ' %1" %2>') text = mw.ustring.gsub(text, "<%s*[Rr][Ee][Ff][^>]*group%s*=%s*[\"']?[^\"'>/]+[\"']%s*>", '<ref>') return text end -- Replace the bold title or synonym near the start of the article by a wikilink to the article function linkBold(text, page) local lang = mw.language.getContentLanguage() local position = mw.ustring.find(text, "'''" .. lang:ucfirst(page) .. "'''", 1, true) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find(text, "'''" .. lang:lcfirst(page) .. "'''", 1, true) -- plain search: special characters in page represent themselves if position then local length = mw.ustring.len(page) text = mw.ustring.sub(text, 1, position + 2) .. "[[" .. mw.ustring.sub(text, position + 3, position + length + 2) .. "]]" .. mw.ustring.sub(text, position + length + 3, -1) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) text = mw.ustring.gsub(text, "()'''(.-'*)'''", function(a, b) if not mw.ustring.find(b, "%[") then -- if not wikilinked return "'''[[" .. page .. "|" .. b .. "]]'''" -- replace '''Foo''' by '''[[page|Foo]]''' else return nil -- instruct gsub to make no change end end, 1) -- "end" here terminates the anonymous replacement function(a, b) passed to gsub end return text end -- Main function for modules local function get(page, options) if options.errors then errors = options.errors end if not page or page == "" then return luaError("noPage") end local text page, section = mw.ustring.match(page, "([^#]+)#?([^#]*)") text, page = getContent(page) if not page then return luaError("noPage") end if not text then return luaError("pageNotFound", page) end local full = text -- save the full text for later if is(options.fragment) then text = getFragment(page, options.fragment) end if is(section) then text = getSection(text, section) end -- Strip text of all undersirables text = cleanupText(text, options) text = parse(text, options) -- Replace the bold title or synonym near the start of the article by a wikilink to the article text = linkBold(text, page) -- Remove '''bold text''' if requested if is(options.nobold) then text = mw.ustring.gsub(text, "'''", "") end -- Keep only tables if requested if is(options.tablesOnly) then text = getTables(text) end -- Keep only lists if requested if is(options.listsOnly) then text = getLists(text) end -- Seek and destroy unterminated templates, links and tags text = fixTemplates(text) text = fixLinks(text) text = fixTags(text, "div") -- Fix broken references if is(options.keepRefs) then text = fixRefs(text, page, full) end return text end -- Main invocation function for templates local function main(frame) local args = parseArgs(frame) local page = args[1] local ok, text = pcall(get, page, args) if not ok then text = d.errors.prefix .. text if d.errorsCategory and d.errorsCategory ~= "" and mw.title.getCurrentTitle().isContentPage then text = text .. '[[' .. d.errorsCategory .. ']]' end return mw.html.create('div'):addClass('error'):wikitext(text) end return frame:preprocess(text) end -- Entry points for templates function p.main(frame) return main(frame) end function p.wikiError(message, value) return wikiError(message, value) end -- Entry points for other Lua modules function p.get(page, options) return get(page, options) end function p.getContent(page) return getContent(page) end function p.getSection(text, section) return getSection(text, section) end function p.getTables(text, options) return getTables(text, options) end function p.getLists(text, options) return getLists(text, options) end function p.parse(text, options) return parse(text, options) end function p.parseImage(text, start) return parseImage(text, start) end function p.parseArgs(frame) return parseArgs(frame) end function p.argImage(text) return argImage(text) end function p.checkImage(image) return checkImage(image) end function p.cleanupText(text, options) return cleanupText(text, options) end function p.luaError(message, value) return luaError(message, value) end function p.is(value) return is(value) end function p.numberFlags(str) return numberFlags(str) end -- Entry points for backwards compatibility function p.getsection(text, section) return getSection(text, section) end function p.parseimage(text, start) return parseImage(text, start) end function p.checkimage(image) return checkImage(image) end function p.argimage(text) return argImage(text) end function p.numberflags(str) return numberFlags(str) end return p qdws7a25ovwxcovcd3pkxzadtghpmek 797333 797332 2020-08-29T15:06:29Z en>Sophivorus 0 Update to latest, see [[Module talk:Excerpt#New version]] 797333 Scribunto text/plain local Transcluder = require('Module:Transcluder') local yesno = require('Module:Yesno') local ok, config = pcall(require, 'Module:Excerpt/config') if not ok then config = {} end local p = {} -- Helper function to get arguments local args function getArg(key, default) value = args[key] if value and mw.text.trim(value) ~= '' then return value end return default end -- Helper function to handle errors function getError(message, value) if type(message) == 'string' then message = Transcluder.getError(message, value) end if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then message:node('[[Category:' .. config.categories.errors .. ']]') end return message end -- Helper function to get localized messages function getMessage(key) local ok, TNT = pcall(require, 'Module:TNT') if not ok then return key end return TNT.format('I18n/Module:Excerpt.tab', key) end function p.main(frame) args = Transcluder.parseArgs(frame) -- Make sure the requested page exists local page = getArg(1) if not page then return getError('no-page') end local title = mw.title.new(page) if not title then return getError('no-page') end if title.isRedirect then title = title.redirectTarget end if not title.exists then return getError('page-not-found', page) end page = title.prefixedText -- Set variables local fragment = getArg('fragment') local section = fragment or getArg(2, getArg('section', mw.ustring.match(getArg(1), '[^#]+#([^#]+)') ) ) local hat = yesno( getArg('hat', true) ) local this = getArg('this') local only = getArg('only') local files = getArg('files') local lists = getArg('lists') local tables = getArg('tables') local sections = not yesno( getArg('sections') ) local templates = table.concat((config.templates or {}), ',') local paragraphs = getArg('paragraphs') local references = getArg('references') local noBold = not yesno( getArg('bold') ) local inline = yesno( getArg('inline') ) local quote = yesno( getArg('quote') ) local more = yesno( getArg('more') ) local class = getArg('class') -- Build the hatnote if hat and not inline then if this then hat = this elseif quote then hat = getMessage('this') elseif only then hat = getMessage(only) else hat = getMessage('section') end hat = hat .. ' ' .. getMessage('excerpt') .. ' ' if section and not fragment then hat = hat .. '[[' .. page .. '#' .. mw.uri.anchorEncode(section) .. '|' .. page .. ' § ' .. mw.ustring.gsub(section, '%[%[([^]|]+)|?[^]]*%]%]', '%1') .. ']]' -- remove nested links else hat = hat .. '[[' .. page .. ']]' end hat = hat .. "''" .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. title:fullUrl('action=edit') .. ' ' .. mw.message.new('editsection'):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' .. "''" local ok, Hatnote = pcall(require, 'Module:Hatnote') if ok then hat = Hatnote._hatnote( hat, { extraclasses = 'dablink excerpt-hat', selfref = true } ) else hat = mw.html.create('div'):addClass('dablink excerpt-hat'):wikitext(hat) end else hat = nil end -- Build the "Read more" link if more and not inline then more = "'''[[" .. page .. '#' .. (section or '') .. "|" .. getMessage('more') .. "]]'''" more = mw.html.create('div'):addClass('noprint excerpt-more'):wikitext(more) else more = nil end -- Build the options for Module:Transcluder out of the template arguments and the desired defaults local options = { files = files, lists = lists, tables = tables, paragraphs = paragraphs, templates = templates ~= '' and '-' .. templates, sections = sections, categories = 0, references = references, only = only and mw.text.trim(only, 's') .. 's', noBold = noBold, noSelfLinks = true, noNonFreeFiles = true, noBehaviorSwitches = true, fixReferences = true, linkBold = true, } -- Get the excerpt itself local title = page .. '#' .. (section or '') local ok, excerpt = pcall(Transcluder.get, title, options) if not ok then return getError(excerpt) end if mw.text.trim(excerpt) == '' then if section then return getError('section-empty', section) else return getError('lead-empty') end end -- Add a line break in case the excerpt starts with a table or list excerpt = '\n' .. excerpt -- If no file was found, try to excerpt one from the removed infoboxes local fileNamespaces = Transcluder.getNamespaces('File') if (files ~= '0' or not files) and not Transcluder.matchAny(excerpt, '%[%[', fileNamespaces, ':') and config.captions then local templates = Transcluder.get(title, { only = 'templates', templates = templates, fixReferences = true } ) local parameters = Transcluder.getParameters(templates) local file, captions, caption for _, pair in pairs(config.captions) do file = pair[1] file = parameters[file] if file and Transcluder.matchAny(file, '^.*%.', {'[Jj][Pp][Ee]?[Gg]','[Pp][Nn][Gg]','[Gg][Ii][Ff]','[Ss][Vv][Gg]'}, '.*') then file = mw.ustring.match(file, '%[?%[?.-:([^{|]+)%]?%]?') or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs(captions) do if parameters[p] then caption = parameters[p] break end end excerpt = '[[File:' .. file .. '|thumb|' .. (caption or '') .. ']]' .. excerpt break end end end -- Remove nested categories excerpt = frame:preprocess(excerpt) local categories, excerpt = Transcluder.getCategories(excerpt, options.categories) -- Add tracking categories if config.categories then local contentCategory = config.categories.content if contentCategory and mw.title.getCurrentTitle().isContentPage then excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ mw.title.getCurrentTitle().namespace ] if namespaceCategory then excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end end -- Load the styles local styles if config.styles then styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) end -- Combine and return the elements local tag1 = 'div' local tag2 = 'div' if inline then tag1 = 'span' tag2 = 'span' elseif quote then tag2 = 'blockquote' end excerpt = mw.html.create(tag1):addClass('excerpt'):wikitext(excerpt) local block = mw.html.create(tag2):addClass('excerpt-block'):addClass(class) return block:node(styles):node(hat):node(excerpt):node(more) end -- Entry points for backwards compatibility function p.lead(frame) return p.main(frame) end function p.excerpt(frame) return p.main(frame) end return p ji6uvuc38hwfqcjiqnzxs5613vomq7g 797334 797333 2020-10-07T14:51:24Z en>Sophivorus 0 797334 Scribunto text/plain local Transcluder = require('Module:Transcluder/sandbox') local yesno = require('Module:Yesno') local ok, config = pcall(require, 'Module:Excerpt/config') if not ok then config = {} end local p = {} -- Helper function to get arguments local args function getArg(key, default) value = args[key] if value and mw.text.trim(value) ~= '' then return value end return default end -- Helper function to handle errors function getError(message, value) if type(message) == 'string' then message = Transcluder.getError(message, value) end if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then message:node('[[Category:' .. config.categories.errors .. ']]') end return message end -- Helper function to get localized messages function getMessage(key) local ok, TNT = pcall(require, 'Module:TNT') if not ok then return key end return TNT.format('I18n/Module:Excerpt.tab', key) end function p.main(frame) args = Transcluder.parseArgs(frame) -- Make sure the requested page exists local page = getArg(1) if not page then return getError('no-page') end local title = mw.title.new(page) if not title then return getError('no-page') end if title.isRedirect then title = title.redirectTarget end if not title.exists then return getError('page-not-found', page) end page = title.prefixedText -- Set variables local fragment = getArg('fragment') local section = fragment or getArg(2, getArg('section', mw.ustring.match(getArg(1), '[^#]+#([^#]+)') ) ) local hat = yesno( getArg('hat', true) ) local this = getArg('this') local only = getArg('only') local files = getArg('files') local lists = getArg('lists') local tables = getArg('tables') local sections = not yesno( getArg('sections') ) local templates = table.concat((config.templates or {}), ',') local paragraphs = getArg('paragraphs') local references = getArg('references') local noBold = not yesno( getArg('bold') ) local inline = yesno( getArg('inline') ) local quote = yesno( getArg('quote') ) local more = yesno( getArg('more') ) local class = getArg('class') -- Build the hatnote if hat and not inline then if this then hat = this elseif quote then hat = getMessage('this') elseif only then hat = getMessage(only) else hat = getMessage('section') end hat = hat .. ' ' .. getMessage('excerpt') .. ' ' if section and not fragment then hat = hat .. '[[' .. page .. '#' .. mw.uri.anchorEncode(section) .. '|' .. page .. ' § ' .. mw.ustring.gsub(section, '%[%[([^]|]+)|?[^]]*%]%]', '%1') .. ']]' -- remove nested links else hat = hat .. '[[' .. page .. ']]' end hat = hat .. "''" .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. title:fullUrl('action=edit') .. ' ' .. mw.message.new('editsection'):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' .. "''" local ok, Hatnote = pcall(require, 'Module:Hatnote') if ok then hat = Hatnote._hatnote( hat, { extraclasses = 'dablink excerpt-hat', selfref = true } ) else hat = mw.html.create('div'):addClass('dablink excerpt-hat'):wikitext(hat) end else hat = nil end -- Build the "Read more" link if more and not inline then more = "'''[[" .. page .. '#' .. (section or '') .. "|" .. getMessage('more') .. "]]'''" more = mw.html.create('div'):addClass('noprint excerpt-more'):wikitext(more) else more = nil end -- Build the options for Module:Transcluder out of the template arguments and the desired defaults local options = { files = files, lists = lists, tables = tables, paragraphs = paragraphs, templates = templates ~= '' and '-' .. templates, sections = sections, categories = 0, references = references, only = only and mw.text.trim(only, 's') .. 's', noBold = noBold, noSelfLinks = true, noNonFreeFiles = true, noBehaviorSwitches = true, fixReferences = true, linkBold = true, } -- Get the excerpt itself local title = page .. '#' .. (section or '') local ok, excerpt = pcall(Transcluder.get, title, options) if not ok then return getError(excerpt) end if mw.text.trim(excerpt) == '' then if section then return getError('section-empty', section) else return getError('lead-empty') end end -- Add a line break in case the excerpt starts with a table or list excerpt = '\n' .. excerpt -- If no file was found, try to excerpt one from the removed infoboxes local fileNamespaces = Transcluder.getNamespaces('File') if (files ~= '0' or not files) and not Transcluder.matchAny(excerpt, '%[%[', fileNamespaces, ':') and config.captions then local templates = Transcluder.get(title, { only = 'templates', templates = templates, fixReferences = true } ) local parameters = Transcluder.getParameters(templates) local file, captions, caption for _, pair in pairs(config.captions) do file = pair[1] file = parameters[file] if file and Transcluder.matchAny(file, '^.*%.', {'[Jj][Pp][Ee]?[Gg]','[Pp][Nn][Gg]','[Gg][Ii][Ff]','[Ss][Vv][Gg]'}, '.*') then file = mw.ustring.match(file, '%[?%[?.-:([^{|]+)%]?%]?') or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs(captions) do if parameters[p] then caption = parameters[p] break end end excerpt = '[[File:' .. file .. '|thumb|' .. (caption or '') .. ']]' .. excerpt break end end end -- Remove nested categories excerpt = frame:preprocess(excerpt) local categories, excerpt = Transcluder.getCategories(excerpt, options.categories) -- Add tracking categories if config.categories then local contentCategory = config.categories.content if contentCategory and mw.title.getCurrentTitle().isContentPage then excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ mw.title.getCurrentTitle().namespace ] if namespaceCategory then excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end end -- Load the styles local styles if config.styles then styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) end -- Combine and return the elements local tag1 = 'div' local tag2 = 'div' if inline then tag1 = 'span' tag2 = 'span' elseif quote then tag2 = 'blockquote' end excerpt = mw.html.create(tag1):addClass('excerpt'):wikitext(excerpt) local block = mw.html.create(tag2):addClass('excerpt-block'):addClass(class) return block:node(styles):node(hat):node(excerpt):node(more) end -- Entry points for backwards compatibility function p.lead(frame) return p.main(frame) end function p.excerpt(frame) return p.main(frame) end return p rtxc1u1r284m54k63p5x1gosu3np9az 797335 797334 2020-10-07T14:52:23Z en>Sophivorus 0 My bad 797335 Scribunto text/plain local Transcluder = require('Module:Transcluder') local yesno = require('Module:Yesno') local ok, config = pcall(require, 'Module:Excerpt/config') if not ok then config = {} end local p = {} -- Helper function to get arguments local args function getArg(key, default) value = args[key] if value and mw.text.trim(value) ~= '' then return value end return default end -- Helper function to handle errors function getError(message, value) if type(message) == 'string' then message = Transcluder.getError(message, value) end if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then message:node('[[Category:' .. config.categories.errors .. ']]') end return message end -- Helper function to get localized messages function getMessage(key) local ok, TNT = pcall(require, 'Module:TNT') if not ok then return key end return TNT.format('I18n/Module:Excerpt.tab', key) end function p.main(frame) args = Transcluder.parseArgs(frame) -- Make sure the requested page exists local page = getArg(1) if not page then return getError('no-page') end local title = mw.title.new(page) if not title then return getError('no-page') end if title.isRedirect then title = title.redirectTarget end if not title.exists then return getError('page-not-found', page) end page = title.prefixedText -- Set variables local fragment = getArg('fragment') local section = fragment or getArg(2, getArg('section', mw.ustring.match(getArg(1), '[^#]+#([^#]+)') ) ) local hat = yesno( getArg('hat', true) ) local this = getArg('this') local only = getArg('only') local files = getArg('files') local lists = getArg('lists') local tables = getArg('tables') local sections = not yesno( getArg('sections') ) local templates = table.concat((config.templates or {}), ',') local paragraphs = getArg('paragraphs') local references = getArg('references') local noBold = not yesno( getArg('bold') ) local inline = yesno( getArg('inline') ) local quote = yesno( getArg('quote') ) local more = yesno( getArg('more') ) local class = getArg('class') -- Build the hatnote if hat and not inline then if this then hat = this elseif quote then hat = getMessage('this') elseif only then hat = getMessage(only) else hat = getMessage('section') end hat = hat .. ' ' .. getMessage('excerpt') .. ' ' if section and not fragment then hat = hat .. '[[' .. page .. '#' .. mw.uri.anchorEncode(section) .. '|' .. page .. ' § ' .. mw.ustring.gsub(section, '%[%[([^]|]+)|?[^]]*%]%]', '%1') .. ']]' -- remove nested links else hat = hat .. '[[' .. page .. ']]' end hat = hat .. "''" .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. title:fullUrl('action=edit') .. ' ' .. mw.message.new('editsection'):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' .. "''" local ok, Hatnote = pcall(require, 'Module:Hatnote') if ok then hat = Hatnote._hatnote( hat, { extraclasses = 'dablink excerpt-hat', selfref = true } ) else hat = mw.html.create('div'):addClass('dablink excerpt-hat'):wikitext(hat) end else hat = nil end -- Build the "Read more" link if more and not inline then more = "'''[[" .. page .. '#' .. (section or '') .. "|" .. getMessage('more') .. "]]'''" more = mw.html.create('div'):addClass('noprint excerpt-more'):wikitext(more) else more = nil end -- Build the options for Module:Transcluder out of the template arguments and the desired defaults local options = { files = files, lists = lists, tables = tables, paragraphs = paragraphs, templates = templates ~= '' and '-' .. templates, sections = sections, categories = 0, references = references, only = only and mw.text.trim(only, 's') .. 's', noBold = noBold, noSelfLinks = true, noNonFreeFiles = true, noBehaviorSwitches = true, fixReferences = true, linkBold = true, } -- Get the excerpt itself local title = page .. '#' .. (section or '') local ok, excerpt = pcall(Transcluder.get, title, options) if not ok then return getError(excerpt) end if mw.text.trim(excerpt) == '' then if section then return getError('section-empty', section) else return getError('lead-empty') end end -- Add a line break in case the excerpt starts with a table or list excerpt = '\n' .. excerpt -- If no file was found, try to excerpt one from the removed infoboxes local fileNamespaces = Transcluder.getNamespaces('File') if (files ~= '0' or not files) and not Transcluder.matchAny(excerpt, '%[%[', fileNamespaces, ':') and config.captions then local templates = Transcluder.get(title, { only = 'templates', templates = templates, fixReferences = true } ) local parameters = Transcluder.getParameters(templates) local file, captions, caption for _, pair in pairs(config.captions) do file = pair[1] file = parameters[file] if file and Transcluder.matchAny(file, '^.*%.', {'[Jj][Pp][Ee]?[Gg]','[Pp][Nn][Gg]','[Gg][Ii][Ff]','[Ss][Vv][Gg]'}, '.*') then file = mw.ustring.match(file, '%[?%[?.-:([^{|]+)%]?%]?') or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs(captions) do if parameters[p] then caption = parameters[p] break end end excerpt = '[[File:' .. file .. '|thumb|' .. (caption or '') .. ']]' .. excerpt break end end end -- Remove nested categories excerpt = frame:preprocess(excerpt) local categories, excerpt = Transcluder.getCategories(excerpt, options.categories) -- Add tracking categories if config.categories then local contentCategory = config.categories.content if contentCategory and mw.title.getCurrentTitle().isContentPage then excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ mw.title.getCurrentTitle().namespace ] if namespaceCategory then excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end end -- Load the styles local styles if config.styles then styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) end -- Combine and return the elements local tag1 = 'div' local tag2 = 'div' if inline then tag1 = 'span' tag2 = 'span' elseif quote then tag2 = 'blockquote' end excerpt = mw.html.create(tag1):addClass('excerpt'):wikitext(excerpt) local block = mw.html.create(tag2):addClass('excerpt-block'):addClass(class) return block:node(styles):node(hat):node(excerpt):node(more) end -- Entry points for backwards compatibility function p.lead(frame) return p.main(frame) end function p.excerpt(frame) return p.main(frame) end return p ji6uvuc38hwfqcjiqnzxs5613vomq7g 797336 797335 2020-10-08T14:15:20Z en>Sophivorus 0 Update to latest 797336 Scribunto text/plain local Transcluder = require('Module:Transcluder') local yesno = require('Module:Yesno') local ok, config = pcall(require, 'Module:Excerpt/config') if not ok then config = {} end local p = {} -- Helper function to get arguments local args function getArg(key, default) value = args[key] if value and mw.text.trim(value) ~= '' then return value end return default end -- Helper function to handle errors function getError(message, value) if type(message) == 'string' then message = Transcluder.getError(message, value) end if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then message:node('[[Category:' .. config.categories.errors .. ']]') end return message end -- Helper function to get localized messages function getMessage(key) local ok, TNT = pcall(require, 'Module:TNT') if not ok then return key end return TNT.format('I18n/Module:Excerpt.tab', key) end function p.main(frame) args = Transcluder.parseArgs(frame) -- Make sure the requested page exists local page = getArg(1) if not page then return getError('no-page') end local title = mw.title.new(page) if not title then return getError('no-page') end if title.isRedirect then title = title.redirectTarget end if not title.exists then return getError('page-not-found', page) end page = title.prefixedText -- Set variables local fragment = getArg('fragment') local section = fragment or getArg(2, getArg('section', mw.ustring.match(getArg(1), '[^#]+#([^#]+)') ) ) local hat = yesno( getArg('hat', true) ) local edit = yesno( getArg('edit', true) ) local this = getArg('this') local only = getArg('only') local files = getArg('files', getArg('file', ( only == 'file' and 1 ) ) ) local lists = getArg('lists', getArg('list', ( only == 'list' and 1 ) ) ) local tables = getArg('tables', getArg('table', ( only == 'table' and 1 ) ) ) local templates = getArg('templates', getArg('template', ( only == 'template' and 1 ) ) ) local paragraphs = getArg('paragraphs', getArg('paragraph', ( only == 'paragraph' and 1 ) ) ) local references = getArg('references', getArg('reference', ( only == 'reference' and 1 ) ) ) local sections = not yesno( getArg('sections') ) local noBold = not yesno( getArg('bold') ) local inline = yesno( getArg('inline') ) local quote = yesno( getArg('quote') ) local more = yesno( getArg('more') ) local class = getArg('class') local blacklist = table.concat((config.templates or {}), ',') -- Build the hatnote if hat and not inline then if this then hat = this elseif quote then hat = getMessage('this') elseif only then hat = getMessage(only) else hat = getMessage('section') end hat = hat .. ' ' .. getMessage('excerpt') .. ' ' if section and not fragment then hat = hat .. '[[:' .. page .. '#' .. mw.uri.anchorEncode(section) .. '|' .. page .. ' § ' .. mw.ustring.gsub(section, '%[%[([^]|]+)|?[^]]*%]%]', '%1') .. ']]' -- remove nested links else hat = hat .. '[[:' .. page .. '|' .. page .. ']]' end if edit then hat = hat .. "''" .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. title:fullUrl('action=edit') .. ' ' .. mw.message.new('editsection'):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' .. "''" end if config.hat then hat = config.hat .. hat .. '}}' hat = frame:preprocess(hat) end hat = mw.html.create('div'):addClass('excerpt-hat'):wikitext(hat) else hat = nil end -- Build the "Read more" link if more and not inline then more = "'''[[" .. page .. '#' .. (section or '') .. "|" .. getMessage('more') .. "]]'''" more = mw.html.create('div'):addClass('noprint excerpt-more'):wikitext(more) else more = nil end -- Build the options for Module:Transcluder out of the template arguments and the desired defaults local options = { files = files, lists = lists, tables = tables, paragraphs = paragraphs, templates = templates or '-' .. blacklist, sections = sections, categories = 0, references = references, only = only and mw.text.trim(only, 's') .. 's', noBold = noBold, noSelfLinks = true, noNonFreeFiles = true, noBehaviorSwitches = true, fixReferences = true, linkBold = true, } -- Get the excerpt itself local title = page .. '#' .. (section or '') local ok, excerpt = pcall(Transcluder.get, title, options) if not ok then return getError(excerpt) end if mw.text.trim(excerpt) == '' then if section then return getError('section-empty', section) else return getError('lead-empty') end end -- Add a line break in case the excerpt starts with a table or list excerpt = '\n' .. excerpt -- If no file was found, try to excerpt one from the removed infoboxes local fileNamespaces = Transcluder.getNamespaces('File') if not only and (files ~= '0' or not files) and not Transcluder.matchAny(excerpt, '%[%[', fileNamespaces, ':') and config.captions then local templates = Transcluder.get(title, { only = 'templates', templates = blacklist, fixReferences = true } ) local parameters = Transcluder.getParameters(templates) local file, captions, caption for _, pair in pairs(config.captions) do file = pair[1] file = parameters[file] if file and Transcluder.matchAny(file, '^.*%.', {'[Jj][Pp][Ee]?[Gg]','[Pp][Nn][Gg]','[Gg][Ii][Ff]','[Ss][Vv][Gg]'}, '.*') then file = mw.ustring.match(file, '%[?%[?.-:([^{|]+)%]?%]?') or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs(captions) do if parameters[p] then caption = parameters[p] break end end excerpt = '[[File:' .. file .. '|thumb|' .. (caption or '') .. ']]' .. excerpt excerpt = Transcluder.removeNonFreeFiles(excerpt) break end end end -- Remove nested categories excerpt = frame:preprocess(excerpt) local categories, excerpt = Transcluder.getCategories(excerpt, options.categories) -- Add tracking categories if config.categories then local contentCategory = config.categories.content if contentCategory and mw.title.getCurrentTitle().isContentPage then excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ mw.title.getCurrentTitle().namespace ] if namespaceCategory then excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end end -- Load the styles local styles if config.styles then styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) end -- Combine and return the elements local tag1 = 'div' local tag2 = 'div' if inline then tag1 = 'span' tag2 = 'span' elseif quote then tag2 = 'blockquote' end excerpt = mw.html.create(tag1):addClass('excerpt'):wikitext(excerpt) local block = mw.html.create(tag2):addClass('excerpt-block'):addClass(class) return block:node(styles):node(hat):node(excerpt):node(more) end -- Entry points for backwards compatibility function p.lead(frame) return p.main(frame) end function p.excerpt(frame) return p.main(frame) end return p 2dbiiu8kj4fias2e4d3fpzhtxk1p2td 797337 797336 2020-10-08T14:34:48Z en>Sophivorus 0 Add dablink 797337 Scribunto text/plain local Transcluder = require('Module:Transcluder') local yesno = require('Module:Yesno') local ok, config = pcall(require, 'Module:Excerpt/config') if not ok then config = {} end local p = {} -- Helper function to get arguments local args function getArg(key, default) value = args[key] if value and mw.text.trim(value) ~= '' then return value end return default end -- Helper function to handle errors function getError(message, value) if type(message) == 'string' then message = Transcluder.getError(message, value) end if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then message:node('[[Category:' .. config.categories.errors .. ']]') end return message end -- Helper function to get localized messages function getMessage(key) local ok, TNT = pcall(require, 'Module:TNT') if not ok then return key end return TNT.format('I18n/Module:Excerpt.tab', key) end function p.main(frame) args = Transcluder.parseArgs(frame) -- Make sure the requested page exists local page = getArg(1) if not page then return getError('no-page') end local title = mw.title.new(page) if not title then return getError('no-page') end if title.isRedirect then title = title.redirectTarget end if not title.exists then return getError('page-not-found', page) end page = title.prefixedText -- Set variables local fragment = getArg('fragment') local section = fragment or getArg(2, getArg('section', mw.ustring.match(getArg(1), '[^#]+#([^#]+)') ) ) local hat = yesno( getArg('hat', true) ) local edit = yesno( getArg('edit', true) ) local this = getArg('this') local only = getArg('only') local files = getArg('files', getArg('file', ( only == 'file' and 1 ) ) ) local lists = getArg('lists', getArg('list', ( only == 'list' and 1 ) ) ) local tables = getArg('tables', getArg('table', ( only == 'table' and 1 ) ) ) local templates = getArg('templates', getArg('template', ( only == 'template' and 1 ) ) ) local paragraphs = getArg('paragraphs', getArg('paragraph', ( only == 'paragraph' and 1 ) ) ) local references = getArg('references', getArg('reference', ( only == 'reference' and 1 ) ) ) local sections = not yesno( getArg('sections') ) local noBold = not yesno( getArg('bold') ) local inline = yesno( getArg('inline') ) local quote = yesno( getArg('quote') ) local more = yesno( getArg('more') ) local class = getArg('class') local blacklist = table.concat((config.templates or {}), ',') -- Build the hatnote if hat and not inline then if this then hat = this elseif quote then hat = getMessage('this') elseif only then hat = getMessage(only) else hat = getMessage('section') end hat = hat .. ' ' .. getMessage('excerpt') .. ' ' if section and not fragment then hat = hat .. '[[:' .. page .. '#' .. mw.uri.anchorEncode(section) .. '|' .. page .. ' § ' .. mw.ustring.gsub(section, '%[%[([^]|]+)|?[^]]*%]%]', '%1') .. ']]' -- remove nested links else hat = hat .. '[[:' .. page .. '|' .. page .. ']]' end if edit then hat = hat .. "''" .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. title:fullUrl('action=edit') .. ' ' .. mw.message.new('editsection'):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' .. "''" end if config.hat then hat = config.hat .. hat .. '}}' hat = frame:preprocess(hat) end hat = mw.html.create('div'):addClass('dablink excerpt-hat'):wikitext(hat) else hat = nil end -- Build the "Read more" link if more and not inline then more = "'''[[" .. page .. '#' .. (section or '') .. "|" .. getMessage('more') .. "]]'''" more = mw.html.create('div'):addClass('noprint excerpt-more'):wikitext(more) else more = nil end -- Build the options for Module:Transcluder out of the template arguments and the desired defaults local options = { files = files, lists = lists, tables = tables, paragraphs = paragraphs, templates = templates or '-' .. blacklist, sections = sections, categories = 0, references = references, only = only and mw.text.trim(only, 's') .. 's', noBold = noBold, noSelfLinks = true, noNonFreeFiles = true, noBehaviorSwitches = true, fixReferences = true, linkBold = true, } -- Get the excerpt itself local title = page .. '#' .. (section or '') local ok, excerpt = pcall(Transcluder.get, title, options) if not ok then return getError(excerpt) end if mw.text.trim(excerpt) == '' then if section then return getError('section-empty', section) else return getError('lead-empty') end end -- Add a line break in case the excerpt starts with a table or list excerpt = '\n' .. excerpt -- If no file was found, try to excerpt one from the removed infoboxes local fileNamespaces = Transcluder.getNamespaces('File') if not only and (files ~= '0' or not files) and not Transcluder.matchAny(excerpt, '%[%[', fileNamespaces, ':') and config.captions then local templates = Transcluder.get(title, { only = 'templates', templates = blacklist, fixReferences = true } ) local parameters = Transcluder.getParameters(templates) local file, captions, caption for _, pair in pairs(config.captions) do file = pair[1] file = parameters[file] if file and Transcluder.matchAny(file, '^.*%.', {'[Jj][Pp][Ee]?[Gg]','[Pp][Nn][Gg]','[Gg][Ii][Ff]','[Ss][Vv][Gg]'}, '.*') then file = mw.ustring.match(file, '%[?%[?.-:([^{|]+)%]?%]?') or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs(captions) do if parameters[p] then caption = parameters[p] break end end excerpt = '[[File:' .. file .. '|thumb|' .. (caption or '') .. ']]' .. excerpt excerpt = Transcluder.removeNonFreeFiles(excerpt) break end end end -- Remove nested categories excerpt = frame:preprocess(excerpt) local categories, excerpt = Transcluder.getCategories(excerpt, options.categories) -- Add tracking categories if config.categories then local contentCategory = config.categories.content if contentCategory and mw.title.getCurrentTitle().isContentPage then excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ mw.title.getCurrentTitle().namespace ] if namespaceCategory then excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end end -- Load the styles local styles if config.styles then styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) end -- Combine and return the elements local tag1 = 'div' local tag2 = 'div' if inline then tag1 = 'span' tag2 = 'span' elseif quote then tag2 = 'blockquote' end excerpt = mw.html.create(tag1):addClass('excerpt'):wikitext(excerpt) local block = mw.html.create(tag2):addClass('excerpt-block'):addClass(class) return block:node(styles):node(hat):node(excerpt):node(more) end -- Entry points for backwards compatibility function p.lead(frame) return p.main(frame) end function p.excerpt(frame) return p.main(frame) end return p fy6651xc7lretv76mrljnkk5g7rb6he 797338 797337 2020-11-17T13:52:59Z en>Sophivorus 0 Update to latest 797338 Scribunto text/plain local Transcluder = require('Module:Transcluder') local yesno = require('Module:Yesno') local ok, config = pcall(require, 'Module:Excerpt/config') if not ok then config = {} end local p = {} -- Helper function to get arguments local args function getArg(key, default) value = args[key] if value and mw.text.trim(value) ~= '' then return value end return default end -- Helper function to handle errors function getError(message, value) if type(message) == 'string' then message = Transcluder.getError(message, value) end if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then message:node('[[Category:' .. config.categories.errors .. ']]') end return message end -- Helper function to get localized messages function getMessage(key) local ok, TNT = pcall(require, 'Module:TNT') if not ok then return key end return TNT.format('I18n/Module:Excerpt.tab', key) end function p.main(frame) args = Transcluder.parseArgs(frame) -- Make sure the requested page exists local page = getArg(1) if not page then return getError('no-page') end local title = mw.title.new(page) if not title then return getError('no-page') end if title.isRedirect then title = title.redirectTarget end if not title.exists then return getError('page-not-found', page) end page = title.prefixedText -- Set variables local fragment = getArg('fragment') local section = fragment or getArg(2, getArg('section', mw.ustring.match(getArg(1), '[^#]+#([^#]+)') ) ) local hat = yesno( getArg('hat', true) ) local edit = yesno( getArg('edit', true) ) local this = getArg('this') local only = getArg('only') local files = getArg('files', getArg('file', ( only == 'file' and 1 ) ) ) local lists = getArg('lists', getArg('list', ( only == 'list' and 1 ) ) ) local tables = getArg('tables', getArg('table', ( only == 'table' and 1 ) ) ) local templates = getArg('templates', getArg('template', ( only == 'template' and 1 ) ) ) local paragraphs = getArg('paragraphs', getArg('paragraph', ( only == 'paragraph' and 1 ) ) ) local references = getArg('references', getArg('reference', ( only == 'reference' and 1 ) ) ) local sections = not yesno( getArg('sections') ) local noBold = not yesno( getArg('bold') ) local inline = yesno( getArg('inline') ) local quote = yesno( getArg('quote') ) local more = yesno( getArg('more') ) local class = getArg('class') local blacklist = table.concat((config.templates or {}), ',') -- Build the hatnote if hat and not inline then if this then hat = this elseif quote then hat = getMessage('this') elseif only then hat = getMessage(only) else hat = getMessage('section') end hat = hat .. ' ' .. getMessage('excerpt') .. ' ' if section and not fragment then hat = hat .. '[[:' .. page .. '#' .. mw.uri.anchorEncode(section) .. '|' .. page .. ' § ' .. mw.ustring.gsub(section, '%[%[([^]|]+)|?[^]]*%]%]', '%1') .. ']]' -- remove nested links else hat = hat .. '[[:' .. page .. '|' .. page .. ']]' end if edit then hat = hat .. "''" .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. title:fullUrl('action=edit') .. ' ' .. mw.message.new('editsection'):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' .. "''" end if config.hat then hat = config.hat .. hat .. '}}' hat = frame:preprocess(hat) end hat = mw.html.create('div'):addClass('dablink excerpt-hat'):wikitext(hat) else hat = nil end -- Build the "Read more" link if more and not inline then more = "'''[[" .. page .. '#' .. (section or '') .. "|" .. getMessage('more') .. "]]'''" more = mw.html.create('div'):addClass('noprint excerpt-more'):wikitext(more) else more = nil end -- Build the options for Module:Transcluder out of the template arguments and the desired defaults local options = { files = files, lists = lists, tables = tables, paragraphs = paragraphs, templates = templates or '-' .. blacklist, sections = sections, categories = 0, references = references, only = only and mw.text.trim(only, 's') .. 's', noBold = noBold, noSelfLinks = true, noNonFreeFiles = true, noBehaviorSwitches = true, fixReferences = true, linkBold = true, } -- Get the excerpt itself local title = page .. '#' .. (section or '') local ok, excerpt = pcall(Transcluder.get, title, options) if not ok then return getError(excerpt) end if mw.text.trim(excerpt) == '' and not only then if section then return getError('section-empty', section) else return getError('lead-empty') end end -- Add a line break in case the excerpt starts with a table or list excerpt = '\n' .. excerpt -- If no file was found, try to excerpt one from the removed infoboxes local fileNamespaces = Transcluder.getNamespaces('File') if ((only == 'file' or only == 'files') or (not only and (files ~= '0' or not files))) and -- caller asked for files not Transcluder.matchAny(excerpt, '%[%[', fileNamespaces, ':') and -- and there are no files in Transcluder's output config.captions -- and we have the config option required to try finding files in templates then local templates = Transcluder.get(title, { only = 'templates', templates = blacklist, fixReferences = true } ) local parameters = Transcluder.getParameters(templates) local file, captions, caption for _, pair in pairs(config.captions) do file = pair[1] file = parameters[file] if file and Transcluder.matchAny(file, '^.*%.', {'[Jj][Pp][Ee]?[Gg]','[Pp][Nn][Gg]','[Gg][Ii][Ff]','[Ss][Vv][Gg]'}, '.*') then file = mw.ustring.match(file, '%[?%[?.-:([^{|]+)%]?%]?') or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs(captions) do if parameters[p] then caption = parameters[p] break end end excerpt = '[[File:' .. file .. '|thumb|' .. (caption or '') .. ']]' .. excerpt excerpt = Transcluder.removeNonFreeFiles(excerpt) break end end end -- Remove nested categories excerpt = frame:preprocess(excerpt) local categories, excerpt = Transcluder.getCategories(excerpt, options.categories) -- Add tracking categories if config.categories then local contentCategory = config.categories.content if contentCategory and mw.title.getCurrentTitle().isContentPage then excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ mw.title.getCurrentTitle().namespace ] if namespaceCategory then excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end end -- Load the styles local styles if config.styles then styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) end -- Combine and return the elements local tag1 = 'div' local tag2 = 'div' if inline then tag1 = 'span' tag2 = 'span' elseif quote then tag2 = 'blockquote' end excerpt = mw.html.create(tag1):addClass('excerpt'):wikitext(excerpt) local block = mw.html.create(tag2):addClass('excerpt-block'):addClass(class) return block:node(styles):node(hat):node(excerpt):node(more) end -- Entry points for backwards compatibility function p.lead(frame) return p.main(frame) end function p.excerpt(frame) return p.main(frame) end return p crts6ogki8oeif8xtmatr9f45u0qncc 797339 797338 2021-02-20T02:45:14Z en>Matt Fitzpatrick 0 implemented edit request by Snaevar, fixes article parameter, which previously did nothing 797339 Scribunto text/plain local Transcluder = require('Module:Transcluder') local yesno = require('Module:Yesno') local ok, config = pcall(require, 'Module:Excerpt/config') if not ok then config = {} end local p = {} -- Helper function to get arguments local args function getArg(key, default) value = args[key] if value and mw.text.trim(value) ~= '' then return value end return default end -- Helper function to handle errors function getError(message, value) if type(message) == 'string' then message = Transcluder.getError(message, value) end if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then message:node('[[Category:' .. config.categories.errors .. ']]') end return message end -- Helper function to get localized messages function getMessage(key) local ok, TNT = pcall(require, 'Module:TNT') if not ok then return key end return TNT.format('I18n/Module:Excerpt.tab', key) end function p.main(frame) args = Transcluder.parseArgs(frame) -- Make sure the requested page exists local page = getArg(1) or getArg('article') if not page then return getError('no-page') end local title = mw.title.new(page) if not title then return getError('no-page') end if title.isRedirect then title = title.redirectTarget end if not title.exists then return getError('page-not-found', page) end page = title.prefixedText -- Set variables local fragment = getArg('fragment') local section = fragment or getArg(2, getArg('section', mw.ustring.match(page, '[^#]+#([^#]+)') ) ) local hat = yesno( getArg('hat', true) ) local edit = yesno( getArg('edit', true) ) local this = getArg('this') local only = getArg('only') local files = getArg('files', getArg('file', ( only == 'file' and 1 ) ) ) local lists = getArg('lists', getArg('list', ( only == 'list' and 1 ) ) ) local tables = getArg('tables', getArg('table', ( only == 'table' and 1 ) ) ) local templates = getArg('templates', getArg('template', ( only == 'template' and 1 ) ) ) local paragraphs = getArg('paragraphs', getArg('paragraph', ( only == 'paragraph' and 1 ) ) ) local references = getArg('references', getArg('reference', ( only == 'reference' and 1 ) ) ) local sections = not yesno( getArg('sections') ) local noBold = not yesno( getArg('bold') ) local inline = yesno( getArg('inline') ) local quote = yesno( getArg('quote') ) local more = yesno( getArg('more') ) local class = getArg('class') local blacklist = table.concat((config.templates or {}), ',') -- Build the hatnote if hat and not inline then if this then hat = this elseif quote then hat = getMessage('this') elseif only then hat = getMessage(only) else hat = getMessage('section') end hat = hat .. ' ' .. getMessage('excerpt') .. ' ' if section and not fragment then hat = hat .. '[[:' .. page .. '#' .. mw.uri.anchorEncode(section) .. '|' .. page .. ' § ' .. mw.ustring.gsub(section, '%[%[([^]|]+)|?[^]]*%]%]', '%1') .. ']]' -- remove nested links else hat = hat .. '[[:' .. page .. '|' .. page .. ']]' end if edit then hat = hat .. "''" .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. title:fullUrl('action=edit') .. ' ' .. mw.message.new('editsection'):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' .. "''" end if config.hat then hat = config.hat .. hat .. '}}' hat = frame:preprocess(hat) end hat = mw.html.create('div'):addClass('dablink excerpt-hat'):wikitext(hat) else hat = nil end -- Build the "Read more" link if more and not inline then more = "'''[[" .. page .. '#' .. (section or '') .. "|" .. getMessage('more') .. "]]'''" more = mw.html.create('div'):addClass('noprint excerpt-more'):wikitext(more) else more = nil end -- Build the options for Module:Transcluder out of the template arguments and the desired defaults local options = { files = files, lists = lists, tables = tables, paragraphs = paragraphs, templates = templates or '-' .. blacklist, sections = sections, categories = 0, references = references, only = only and mw.text.trim(only, 's') .. 's', noBold = noBold, noSelfLinks = true, noNonFreeFiles = true, noBehaviorSwitches = true, fixReferences = true, linkBold = true, } -- Get the excerpt itself local title = page .. '#' .. (section or '') local ok, excerpt = pcall(Transcluder.get, title, options) if not ok then return getError(excerpt) end if mw.text.trim(excerpt) == '' and not only then if section then return getError('section-empty', section) else return getError('lead-empty') end end -- Add a line break in case the excerpt starts with a table or list excerpt = '\n' .. excerpt -- If no file was found, try to excerpt one from the removed infoboxes local fileNamespaces = Transcluder.getNamespaces('File') if ((only == 'file' or only == 'files') or (not only and (files ~= '0' or not files))) and -- caller asked for files not Transcluder.matchAny(excerpt, '%[%[', fileNamespaces, ':') and -- and there are no files in Transcluder's output config.captions -- and we have the config option required to try finding files in templates then local templates = Transcluder.get(title, { only = 'templates', templates = blacklist, fixReferences = true } ) local parameters = Transcluder.getParameters(templates) local file, captions, caption for _, pair in pairs(config.captions) do file = pair[1] file = parameters[file] if file and Transcluder.matchAny(file, '^.*%.', {'[Jj][Pp][Ee]?[Gg]','[Pp][Nn][Gg]','[Gg][Ii][Ff]','[Ss][Vv][Gg]'}, '.*') then file = mw.ustring.match(file, '%[?%[?.-:([^{|]+)%]?%]?') or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs(captions) do if parameters[p] then caption = parameters[p] break end end excerpt = '[[File:' .. file .. '|thumb|' .. (caption or '') .. ']]' .. excerpt excerpt = Transcluder.removeNonFreeFiles(excerpt) break end end end -- Remove nested categories excerpt = frame:preprocess(excerpt) local categories, excerpt = Transcluder.getCategories(excerpt, options.categories) -- Add tracking categories if config.categories then local contentCategory = config.categories.content if contentCategory and mw.title.getCurrentTitle().isContentPage then excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ mw.title.getCurrentTitle().namespace ] if namespaceCategory then excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end end -- Load the styles local styles if config.styles then styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) end -- Combine and return the elements local tag1 = 'div' local tag2 = 'div' if inline then tag1 = 'span' tag2 = 'span' elseif quote then tag2 = 'blockquote' end excerpt = mw.html.create(tag1):addClass('excerpt'):wikitext(excerpt) local block = mw.html.create(tag2):addClass('excerpt-block'):addClass(class) return block:node(styles):node(hat):node(excerpt):node(more) end -- Entry points for backwards compatibility function p.lead(frame) return p.main(frame) end function p.excerpt(frame) return p.main(frame) end return p gq92jlur3vhul28zvgmukbf57ped2ll 797340 797339 2021-02-22T21:40:47Z en>Sophivorus 0 Fix the previous fix 797340 Scribunto text/plain local Transcluder = require('Module:Transcluder') local yesno = require('Module:Yesno') local ok, config = pcall(require, 'Module:Excerpt/config') if not ok then config = {} end local p = {} -- Helper function to get arguments local args function getArg(key, default) value = args[key] if value and mw.text.trim(value) ~= '' then return value end return default end -- Helper function to handle errors function getError(message, value) if type(message) == 'string' then message = Transcluder.getError(message, value) end if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then message:node('[[Category:' .. config.categories.errors .. ']]') end return message end -- Helper function to get localized messages function getMessage(key) local ok, TNT = pcall(require, 'Module:TNT') if not ok then return key end return TNT.format('I18n/Module:Excerpt.tab', key) end function p.main(frame) args = Transcluder.parseArgs(frame) -- Make sure the requested page exists local page = getArg(1, getArg('article') ) if not page then return getError('no-page') end local title = mw.title.new(page) if not title then return getError('no-page') end if title.isRedirect then title = title.redirectTarget end if not title.exists then return getError('page-not-found', page) end page = title.prefixedText -- Set variables local fragment = getArg('fragment') local section = fragment or getArg(2, getArg('section', mw.ustring.match( getArg(1, getArg('article') ), '[^#]+#([^#]+)') ) ) local hat = yesno( getArg('hat', true) ) local edit = yesno( getArg('edit', true) ) local this = getArg('this') local only = getArg('only') local files = getArg('files', getArg('file', ( only == 'file' and 1 ) ) ) local lists = getArg('lists', getArg('list', ( only == 'list' and 1 ) ) ) local tables = getArg('tables', getArg('table', ( only == 'table' and 1 ) ) ) local templates = getArg('templates', getArg('template', ( only == 'template' and 1 ) ) ) local paragraphs = getArg('paragraphs', getArg('paragraph', ( only == 'paragraph' and 1 ) ) ) local references = getArg('references', getArg('reference', ( only == 'reference' and 1 ) ) ) local sections = not yesno( getArg('sections') ) local noBold = not yesno( getArg('bold') ) local inline = yesno( getArg('inline') ) local quote = yesno( getArg('quote') ) local more = yesno( getArg('more') ) local class = getArg('class') local blacklist = table.concat((config.templates or {}), ',') -- Build the hatnote if hat and not inline then if this then hat = this elseif quote then hat = getMessage('this') elseif only then hat = getMessage(only) else hat = getMessage('section') end hat = hat .. ' ' .. getMessage('excerpt') .. ' ' if section and not fragment then hat = hat .. '[[:' .. page .. '#' .. mw.uri.anchorEncode(section) .. '|' .. page .. ' § ' .. mw.ustring.gsub(section, '%[%[([^]|]+)|?[^]]*%]%]', '%1') .. ']]' -- remove nested links else hat = hat .. '[[:' .. page .. '|' .. page .. ']]' end if edit then hat = hat .. "''" .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. title:fullUrl('action=edit') .. ' ' .. mw.message.new('editsection'):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' .. "''" end if config.hat then hat = config.hat .. hat .. '}}' hat = frame:preprocess(hat) end hat = mw.html.create('div'):addClass('dablink excerpt-hat'):wikitext(hat) else hat = nil end -- Build the "Read more" link if more and not inline then more = "'''[[" .. page .. '#' .. (section or '') .. "|" .. getMessage('more') .. "]]'''" more = mw.html.create('div'):addClass('noprint excerpt-more'):wikitext(more) else more = nil end -- Build the options for Module:Transcluder out of the template arguments and the desired defaults local options = { files = files, lists = lists, tables = tables, paragraphs = paragraphs, templates = templates or '-' .. blacklist, sections = sections, categories = 0, references = references, only = only and mw.text.trim(only, 's') .. 's', noBold = noBold, noSelfLinks = true, noNonFreeFiles = true, noBehaviorSwitches = true, fixReferences = true, linkBold = true, } -- Get the excerpt itself local title = page .. '#' .. (section or '') local ok, excerpt = pcall(Transcluder.get, title, options) if not ok then return getError(excerpt) end if mw.text.trim(excerpt) == '' and not only then if section then return getError('section-empty', section) else return getError('lead-empty') end end -- Add a line break in case the excerpt starts with a table or list excerpt = '\n' .. excerpt -- If no file was found, try to excerpt one from the removed infoboxes local fileNamespaces = Transcluder.getNamespaces('File') if ((only == 'file' or only == 'files') or (not only and (files ~= '0' or not files))) and -- caller asked for files not Transcluder.matchAny(excerpt, '%[%[', fileNamespaces, ':') and -- and there are no files in Transcluder's output config.captions -- and we have the config option required to try finding files in templates then local templates = Transcluder.get(title, { only = 'templates', templates = blacklist, fixReferences = true } ) local parameters = Transcluder.getParameters(templates) local file, captions, caption for _, pair in pairs(config.captions) do file = pair[1] file = parameters[file] if file and Transcluder.matchAny(file, '^.*%.', {'[Jj][Pp][Ee]?[Gg]','[Pp][Nn][Gg]','[Gg][Ii][Ff]','[Ss][Vv][Gg]'}, '.*') then file = mw.ustring.match(file, '%[?%[?.-:([^{|]+)%]?%]?') or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs(captions) do if parameters[p] then caption = parameters[p] break end end excerpt = '[[File:' .. file .. '|thumb|' .. (caption or '') .. ']]' .. excerpt excerpt = Transcluder.removeNonFreeFiles(excerpt) break end end end -- Remove nested categories excerpt = frame:preprocess(excerpt) local categories, excerpt = Transcluder.getCategories(excerpt, options.categories) -- Add tracking categories if config.categories then local contentCategory = config.categories.content if contentCategory and mw.title.getCurrentTitle().isContentPage then excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ mw.title.getCurrentTitle().namespace ] if namespaceCategory then excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end end -- Load the styles local styles if config.styles then styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) end -- Combine and return the elements local tag1 = 'div' local tag2 = 'div' if inline then tag1 = 'span' tag2 = 'span' elseif quote then tag2 = 'blockquote' end excerpt = mw.html.create(tag1):addClass('excerpt'):wikitext(excerpt) local block = mw.html.create(tag2):addClass('excerpt-block'):addClass(class) return block:node(styles):node(hat):node(excerpt):node(more) end -- Entry points for backwards compatibility function p.lead(frame) return p.main(frame) end function p.excerpt(frame) return p.main(frame) end return p 1aq7d5meylpdjh8h4n9qane9y2wc7r5 797341 797340 2021-08-22T14:37:49Z en>Sophivorus 0 Update to latest 797341 Scribunto text/plain local Transcluder = require('Module:Transcluder') local yesno = require('Module:Yesno') local ok, config = pcall(require, 'Module:Excerpt/config') if not ok then config = {} end local p = {} -- Helper function to get arguments local args function getArg(key, default) value = args[key] if value and mw.text.trim(value) ~= '' then return value end return default end -- Helper function to handle errors function getError(message, value) if type(message) == 'string' then message = Transcluder.getError(message, value) end if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then message:node('[[Category:' .. config.categories.errors .. ']]') end return message end -- Helper function to get localized messages function getMessage(key) local ok, TNT = pcall(require, 'Module:TNT') if not ok then return key end return TNT.format('I18n/Module:Excerpt.tab', key) end function p.main(frame) args = Transcluder.parseArgs(frame) -- Make sure the requested page exists local page = getArg(1, getArg('article') ) if not page then return getError('no-page') end local title = mw.title.new(page) if not title then return getError('no-page') end if title.isRedirect then title = title.redirectTarget end if not title.exists then return getError('page-not-found', page) end page = title.prefixedText -- Set variables local fragment = getArg('fragment') local section = fragment or getArg(2, getArg('section', mw.ustring.match( getArg(1, getArg('article') ), '[^#]+#([^#]+)') ) ) local hat = yesno( getArg('hat', true) ) local edit = yesno( getArg('edit', true) ) local this = getArg('this') local only = getArg('only') local files = getArg('files', getArg('file', ( only == 'file' and 1 ) ) ) local lists = getArg('lists', getArg('list', ( only == 'list' and 1 ) ) ) local tables = getArg('tables', getArg('table', ( only == 'table' and 1 ) ) ) local templates = getArg('templates', getArg('template', ( only == 'template' and 1 ) ) ) local paragraphs = getArg('paragraphs', getArg('paragraph', ( only == 'paragraph' and 1 ) ) ) local references = getArg('references', getArg('reference', ( only == 'reference' and 1 ) ) ) local sections = not yesno( getArg('sections') ) local noBold = not yesno( getArg('bold') ) local inline = yesno( getArg('inline') ) local quote = yesno( getArg('quote') ) local more = yesno( getArg('more') ) local class = getArg('class') local blacklist = table.concat((config.templates or {}), ',') -- Build the hatnote if hat and not inline then if this then hat = this elseif quote then hat = getMessage('this') elseif only then hat = getMessage(only) else hat = getMessage('section') end hat = hat .. ' ' .. getMessage('excerpt') .. ' ' if section and not fragment then hat = hat .. '[[:' .. page .. '#' .. mw.uri.anchorEncode(section) .. '|' .. page .. ' § ' .. mw.ustring.gsub(section, '%[%[([^]|]+)|?[^]]*%]%]', '%1') .. ']].' -- remove nested links else hat = hat .. '[[:' .. page .. '|' .. page .. ']].' end if edit then hat = hat .. "''" .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. title:fullUrl('action=edit') .. ' ' .. mw.message.new('editsection'):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' .. "''" end if config.hat then hat = config.hat .. hat .. '}}' hat = frame:preprocess(hat) else hat = mw.html.create('div'):addClass('dablink excerpt-hat'):wikitext(hat) end else hat = nil end -- Build the "Read more" link if more and not inline then more = "'''[[" .. page .. '#' .. (section or '') .. "|" .. getMessage('more') .. "]]'''" more = mw.html.create('div'):addClass('noprint excerpt-more'):wikitext(more) else more = nil end -- Build the options for Module:Transcluder out of the template arguments and the desired defaults local options = { files = files, lists = lists, tables = tables, paragraphs = paragraphs, templates = templates or '-' .. blacklist, sections = sections, categories = 0, references = references, only = only and mw.text.trim(only, 's') .. 's', noBold = noBold, noSelfLinks = true, noNonFreeFiles = true, noBehaviorSwitches = true, fixReferences = true, linkBold = true, } -- Get the excerpt itself local title = page .. '#' .. (section or '') local ok, excerpt = pcall(Transcluder.get, title, options) if not ok then return getError(excerpt) end if mw.text.trim(excerpt) == '' and not only then if section then return getError('section-empty', section) else return getError('lead-empty') end end -- Add a line break in case the excerpt starts with a table or list excerpt = '\n' .. excerpt -- If no file was found, try to excerpt one from the removed infoboxes local fileNamespaces = Transcluder.getNamespaces('File') if ((only == 'file' or only == 'files') or (not only and (files ~= '0' or not files))) and -- caller asked for files not Transcluder.matchAny(excerpt, '%[%[', fileNamespaces, ':') and -- and there are no files in Transcluder's output config.captions -- and we have the config option required to try finding files in templates then local templates = Transcluder.get(title, { only = 'templates', templates = blacklist, fixReferences = true } ) local parameters = Transcluder.getParameters(templates) local file, captions, caption for _, pair in pairs(config.captions) do file = pair[1] file = parameters[file] if file and Transcluder.matchAny(file, '^.*%.', {'[Jj][Pp][Ee]?[Gg]','[Pp][Nn][Gg]','[Gg][Ii][Ff]','[Ss][Vv][Gg]'}, '.*') then file = mw.ustring.match(file, '%[?%[?.-:([^{|]+)%]?%]?') or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs(captions) do if parameters[p] then caption = parameters[p] break end end excerpt = '[[File:' .. file .. '|thumb|' .. (caption or '') .. ']]' .. excerpt excerpt = Transcluder.removeNonFreeFiles(excerpt) break end end end -- Remove nested categories excerpt = frame:preprocess(excerpt) local categories, excerpt = Transcluder.getCategories(excerpt, options.categories) -- Add tracking categories if config.categories then local contentCategory = config.categories.content if contentCategory and mw.title.getCurrentTitle().isContentPage then excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ mw.title.getCurrentTitle().namespace ] if namespaceCategory then excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end end -- Load the styles local styles if config.styles then styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) end -- Combine and return the elements local tag1 = 'div' local tag2 = 'div' if inline then tag1 = 'span' tag2 = 'span' elseif quote then tag2 = 'blockquote' end excerpt = mw.html.create(tag1):addClass('excerpt'):wikitext(excerpt) local block = mw.html.create(tag2):addClass('excerpt-block'):addClass(class) return block:node(styles):node(hat):node(excerpt):node(more) end -- Entry points for backwards compatibility function p.lead(frame) return p.main(frame) end function p.excerpt(frame) return p.main(frame) end return p popak3dwz0358mnxybk9sz9kn7u786u 797342 797341 2022-01-20T21:11:31Z en>Sophivorus 0 Change parameter name to "subsections" per being more intuitive to its intended use 797342 Scribunto text/plain local Transcluder = require('Module:Transcluder') local yesno = require('Module:Yesno') local ok, config = pcall(require, 'Module:Excerpt/config') if not ok then config = {} end local p = {} -- Helper function to get arguments local args function getArg(key, default) value = args[key] if value and mw.text.trim(value) ~= '' then return value end return default end -- Helper function to handle errors function getError(message, value) if type(message) == 'string' then message = Transcluder.getError(message, value) end if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then message:node('[[Category:' .. config.categories.errors .. ']]') end return message end -- Helper function to get localized messages function getMessage(key) local ok, TNT = pcall(require, 'Module:TNT') if not ok then return key end return TNT.format('I18n/Module:Excerpt.tab', key) end function p.main(frame) args = Transcluder.parseArgs(frame) -- Make sure the requested page exists local page = getArg(1, getArg('article') ) if not page then return getError('no-page') end local title = mw.title.new(page) if not title then return getError('no-page') end if title.isRedirect then title = title.redirectTarget end if not title.exists then return getError('page-not-found', page) end page = title.prefixedText -- Set variables local fragment = getArg('fragment') local section = fragment or getArg(2, getArg('section', mw.ustring.match( getArg(1, getArg('article') ), '[^#]+#([^#]+)') ) ) local hat = yesno( getArg('hat', true) ) local edit = yesno( getArg('edit', true) ) local this = getArg('this') local only = getArg('only') local files = getArg('files', getArg('file', ( only == 'file' and 1 ) ) ) local lists = getArg('lists', getArg('list', ( only == 'list' and 1 ) ) ) local tables = getArg('tables', getArg('table', ( only == 'table' and 1 ) ) ) local templates = getArg('templates', getArg('template', ( only == 'template' and 1 ) ) ) local paragraphs = getArg('paragraphs', getArg('paragraph', ( only == 'paragraph' and 1 ) ) ) local references = getArg('references', getArg('reference', ( only == 'reference' and 1 ) ) ) local subsections = not yesno( getArg('subsections') ) local noBold = not yesno( getArg('bold') ) local inline = yesno( getArg('inline') ) local quote = yesno( getArg('quote') ) local more = yesno( getArg('more') ) local class = getArg('class') local blacklist = table.concat((config.templates or {}), ',') -- Build the hatnote if hat and not inline then if this then hat = this elseif quote then hat = getMessage('this') elseif only then hat = getMessage(only) else hat = getMessage('section') end hat = hat .. ' ' .. getMessage('excerpt') .. ' ' if section and not fragment then hat = hat .. '[[:' .. page .. '#' .. mw.uri.anchorEncode(section) .. '|' .. page .. ' § ' .. mw.ustring.gsub(section, '%[%[([^]|]+)|?[^]]*%]%]', '%1') .. ']].' -- remove nested links else hat = hat .. '[[:' .. page .. '|' .. page .. ']].' end if edit then hat = hat .. "''" .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. title:fullUrl('action=edit') .. ' ' .. mw.message.new('editsection'):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' .. "''" end if config.hat then hat = config.hat .. hat .. '}}' hat = frame:preprocess(hat) else hat = mw.html.create('div'):addClass('dablink excerpt-hat'):wikitext(hat) end else hat = nil end -- Build the "Read more" link if more and not inline then more = "'''[[" .. page .. '#' .. (section or '') .. "|" .. getMessage('more') .. "]]'''" more = mw.html.create('div'):addClass('noprint excerpt-more'):wikitext(more) else more = nil end -- Build the options for Module:Transcluder out of the template arguments and the desired defaults local options = { files = files, lists = lists, tables = tables, paragraphs = paragraphs, templates = templates or '-' .. blacklist, sections = subsections, categories = 0, references = references, only = only and mw.text.trim(only, 's') .. 's', noBold = noBold, noSelfLinks = true, noNonFreeFiles = true, noBehaviorSwitches = true, fixReferences = true, linkBold = true, } -- Get the excerpt itself local title = page .. '#' .. (section or '') local ok, excerpt = pcall(Transcluder.get, title, options) if not ok then return getError(excerpt) end if mw.text.trim(excerpt) == '' and not only then if section then return getError('section-empty', section) else return getError('lead-empty') end end -- Add a line break in case the excerpt starts with a table or list excerpt = '\n' .. excerpt -- If no file was found, try to excerpt one from the removed infoboxes local fileNamespaces = Transcluder.getNamespaces('File') if ((only == 'file' or only == 'files') or (not only and (files ~= '0' or not files))) and -- caller asked for files not Transcluder.matchAny(excerpt, '%[%[', fileNamespaces, ':') and -- and there are no files in Transcluder's output config.captions -- and we have the config option required to try finding files in templates then local templates = Transcluder.get(title, { only = 'templates', templates = blacklist, fixReferences = true } ) local parameters = Transcluder.getParameters(templates) local file, captions, caption for _, pair in pairs(config.captions) do file = pair[1] file = parameters[file] if file and Transcluder.matchAny(file, '^.*%.', {'[Jj][Pp][Ee]?[Gg]','[Pp][Nn][Gg]','[Gg][Ii][Ff]','[Ss][Vv][Gg]'}, '.*') then file = mw.ustring.match(file, '%[?%[?.-:([^{|]+)%]?%]?') or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs(captions) do if parameters[p] then caption = parameters[p] break end end excerpt = '[[File:' .. file .. '|thumb|' .. (caption or '') .. ']]' .. excerpt excerpt = Transcluder.removeNonFreeFiles(excerpt) break end end end -- Remove nested categories excerpt = frame:preprocess(excerpt) local categories, excerpt = Transcluder.getCategories(excerpt, options.categories) -- Add tracking categories if config.categories then local contentCategory = config.categories.content if contentCategory and mw.title.getCurrentTitle().isContentPage then excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ mw.title.getCurrentTitle().namespace ] if namespaceCategory then excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end end -- Load the styles local styles if config.styles then styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) end -- Combine and return the elements local tag1 = 'div' local tag2 = 'div' if inline then tag1 = 'span' tag2 = 'span' elseif quote then tag2 = 'blockquote' end excerpt = mw.html.create(tag1):addClass('excerpt'):wikitext(excerpt) local block = mw.html.create(tag2):addClass('excerpt-block'):addClass(class) return block:node(styles):node(hat):node(excerpt):node(more) end -- Entry points for backwards compatibility function p.lead(frame) return p.main(frame) end function p.excerpt(frame) return p.main(frame) end return p cemmvi2egtwqd9r3rezd8t166k7sfhd 797343 797342 2022-01-20T21:32:11Z en>Sophivorus 0 Remove use of italics that was specific to this wiki and fix it via local stylesheet instead 797343 Scribunto text/plain local Transcluder = require('Module:Transcluder') local yesno = require('Module:Yesno') local ok, config = pcall(require, 'Module:Excerpt/config') if not ok then config = {} end local p = {} -- Helper function to get arguments local args function getArg(key, default) value = args[key] if value and mw.text.trim(value) ~= '' then return value end return default end -- Helper function to handle errors function getError(message, value) if type(message) == 'string' then message = Transcluder.getError(message, value) end if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then message:node('[[Category:' .. config.categories.errors .. ']]') end return message end -- Helper function to get localized messages function getMessage(key) local ok, TNT = pcall(require, 'Module:TNT') if not ok then return key end return TNT.format('I18n/Module:Excerpt.tab', key) end function p.main(frame) args = Transcluder.parseArgs(frame) -- Make sure the requested page exists local page = getArg(1, getArg('article') ) if not page then return getError('no-page') end local title = mw.title.new(page) if not title then return getError('no-page') end if title.isRedirect then title = title.redirectTarget end if not title.exists then return getError('page-not-found', page) end page = title.prefixedText -- Set variables local fragment = getArg('fragment') local section = fragment or getArg(2, getArg('section', mw.ustring.match( getArg(1, getArg('article') ), '[^#]+#([^#]+)') ) ) local hat = yesno( getArg('hat', true) ) local edit = yesno( getArg('edit', true) ) local this = getArg('this') local only = getArg('only') local files = getArg('files', getArg('file', ( only == 'file' and 1 ) ) ) local lists = getArg('lists', getArg('list', ( only == 'list' and 1 ) ) ) local tables = getArg('tables', getArg('table', ( only == 'table' and 1 ) ) ) local templates = getArg('templates', getArg('template', ( only == 'template' and 1 ) ) ) local paragraphs = getArg('paragraphs', getArg('paragraph', ( only == 'paragraph' and 1 ) ) ) local references = getArg('references', getArg('reference', ( only == 'reference' and 1 ) ) ) local subsections = not yesno( getArg('subsections') ) local noBold = not yesno( getArg('bold') ) local inline = yesno( getArg('inline') ) local quote = yesno( getArg('quote') ) local more = yesno( getArg('more') ) local class = getArg('class') local blacklist = table.concat((config.templates or {}), ',') -- Build the hatnote if hat and not inline then if this then hat = this elseif quote then hat = getMessage('this') elseif only then hat = getMessage(only) else hat = getMessage('section') end hat = hat .. ' ' .. getMessage('excerpt') .. ' ' if section and not fragment then hat = hat .. '[[:' .. page .. '#' .. mw.uri.anchorEncode(section) .. '|' .. page .. ' § ' .. mw.ustring.gsub(section, '%[%[([^]|]+)|?[^]]*%]%]', '%1') .. ']].' -- remove nested links else hat = hat .. '[[:' .. page .. '|' .. page .. ']].' end if edit then hat = hat .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. title:fullUrl('action=edit') .. ' ' .. mw.message.new('editsection'):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' end if config.hat then hat = config.hat .. hat .. '}}' hat = frame:preprocess(hat) else hat = mw.html.create('div'):addClass('dablink excerpt-hat'):wikitext(hat) end else hat = nil end -- Build the "Read more" link if more and not inline then more = "'''[[" .. page .. '#' .. (section or '') .. "|" .. getMessage('more') .. "]]'''" more = mw.html.create('div'):addClass('noprint excerpt-more'):wikitext(more) else more = nil end -- Build the options for Module:Transcluder out of the template arguments and the desired defaults local options = { files = files, lists = lists, tables = tables, paragraphs = paragraphs, templates = templates or '-' .. blacklist, sections = subsections, categories = 0, references = references, only = only and mw.text.trim(only, 's') .. 's', noBold = noBold, noSelfLinks = true, noNonFreeFiles = true, noBehaviorSwitches = true, fixReferences = true, linkBold = true, } -- Get the excerpt itself local title = page .. '#' .. (section or '') local ok, excerpt = pcall(Transcluder.get, title, options) if not ok then return getError(excerpt) end if mw.text.trim(excerpt) == '' and not only then if section then return getError('section-empty', section) else return getError('lead-empty') end end -- Add a line break in case the excerpt starts with a table or list excerpt = '\n' .. excerpt -- If no file was found, try to excerpt one from the removed infoboxes local fileNamespaces = Transcluder.getNamespaces('File') if ((only == 'file' or only == 'files') or (not only and (files ~= '0' or not files))) and -- caller asked for files not Transcluder.matchAny(excerpt, '%[%[', fileNamespaces, ':') and -- and there are no files in Transcluder's output config.captions -- and we have the config option required to try finding files in templates then local templates = Transcluder.get(title, { only = 'templates', templates = blacklist, fixReferences = true } ) local parameters = Transcluder.getParameters(templates) local file, captions, caption for _, pair in pairs(config.captions) do file = pair[1] file = parameters[file] if file and Transcluder.matchAny(file, '^.*%.', {'[Jj][Pp][Ee]?[Gg]','[Pp][Nn][Gg]','[Gg][Ii][Ff]','[Ss][Vv][Gg]'}, '.*') then file = mw.ustring.match(file, '%[?%[?.-:([^{|]+)%]?%]?') or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs(captions) do if parameters[p] then caption = parameters[p] break end end excerpt = '[[File:' .. file .. '|thumb|' .. (caption or '') .. ']]' .. excerpt excerpt = Transcluder.removeNonFreeFiles(excerpt) break end end end -- Remove nested categories excerpt = frame:preprocess(excerpt) local categories, excerpt = Transcluder.getCategories(excerpt, options.categories) -- Add tracking categories if config.categories then local contentCategory = config.categories.content if contentCategory and mw.title.getCurrentTitle().isContentPage then excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ mw.title.getCurrentTitle().namespace ] if namespaceCategory then excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end end -- Load the styles local styles if config.styles then styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) end -- Combine and return the elements local tag1 = 'div' local tag2 = 'div' if inline then tag1 = 'span' tag2 = 'span' elseif quote then tag2 = 'blockquote' end excerpt = mw.html.create(tag1):addClass('excerpt'):wikitext(excerpt) local block = mw.html.create(tag2):addClass('excerpt-block'):addClass(class) return block:node(styles):node(hat):node(excerpt):node(more) end -- Entry points for backwards compatibility function p.lead(frame) return p.main(frame) end function p.excerpt(frame) return p.main(frame) end return p bapkyy37t2vkevw0rrsg5ucn53t3zx7 797344 797343 2022-01-21T20:15:29Z en>Sophivorus 0 Update to latest, see talk page 797344 Scribunto text/plain local Transcluder = require('Module:Transcluder') local yesno = require('Module:Yesno') local ok, config = pcall(require, 'Module:Excerpt/config') if not ok then config = {} end local p = {} -- Helper function to get arguments local args function getArg(key, default) value = args[key] if value and mw.text.trim(value) ~= '' then return value end return default end -- Helper function to handle errors function getError(message, value) if type(message) == 'string' then message = Transcluder.getError(message, value) end if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then message:node('[[Category:' .. config.categories.errors .. ']]') end return message end -- Helper function to get localized messages function getMessage(key) local ok, TNT = pcall(require, 'Module:TNT') if not ok then return key end return TNT.format('I18n/Module:Excerpt.tab', key) end function p.main(frame) args = Transcluder.parseArgs(frame) -- Make sure the requested page exists local page = getArg(1, getArg('article') ) if not page then return getError('no-page') end local title = mw.title.new(page) if not title then return getError('no-page') end if title.isRedirect then title = title.redirectTarget end if not title.exists then return getError('page-not-found', page) end page = title.prefixedText -- Set variables local fragment = getArg('fragment') local section = fragment or getArg(2, getArg('section', mw.ustring.match( getArg(1, getArg('article') ), '[^#]+#([^#]+)') ) ) local hat = yesno( getArg('hat', true) ) local edit = yesno( getArg('edit', true) ) local this = getArg('this') local only = getArg('only') local files = getArg('files', getArg('file', ( only == 'file' and 1 ) ) ) local lists = getArg('lists', getArg('list', ( only == 'list' and 1 ) ) ) local tables = getArg('tables', getArg('table', ( only == 'table' and 1 ) ) ) local templates = getArg('templates', getArg('template', ( only == 'template' and 1 ) ) ) local paragraphs = getArg('paragraphs', getArg('paragraph', ( only == 'paragraph' and 1 ) ) ) local references = getArg('references', getArg('reference', ( only == 'reference' and 1 ) ) ) local subsections = not yesno( getArg('subsections') ) local noBold = not yesno( getArg('bold') ) local inline = yesno( getArg('inline') ) local quote = yesno( getArg('quote') ) local more = yesno( getArg('more') ) local class = getArg('class') -- Build the hatnote if hat and not inline then if this then hat = this elseif quote then hat = getMessage('this') elseif only then hat = getMessage(only) else hat = getMessage('section') end hat = hat .. ' ' .. getMessage('excerpt') .. ' ' if section and not fragment then hat = hat .. '[[:' .. page .. '#' .. mw.uri.anchorEncode(section) .. '|' .. page .. ' § ' .. mw.ustring.gsub(section, '%[%[([^]|]+)|?[^]]*%]%]', '%1') .. ']].' -- remove nested links else hat = hat .. '[[:' .. page .. '|' .. page .. ']].' end if edit then hat = hat .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. title:fullUrl('action=edit') .. ' ' .. mw.message.new('editsection'):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' end if config.hat then hat = config.hat .. hat .. '}}' hat = frame:preprocess(hat) else hat = mw.html.create('div'):addClass('dablink excerpt-hat'):wikitext(hat) end else hat = nil end -- Build the "Read more" link if more and not inline then more = "'''[[" .. page .. '#' .. (section or '') .. "|" .. getMessage('more') .. "]]'''" more = mw.html.create('div'):addClass('noprint excerpt-more'):wikitext(more) else more = nil end -- Build the templates option local blacklist = table.concat((config.blacklist or config.templates or {}), ',') if templates then if string.sub(templates, 1, 1) == '-' then templates = templates .. ',' .. blacklist end else templates = '-' .. blacklist end -- Build the options for Module:Transcluder out of the template arguments and the desired defaults local options = { files = files, lists = lists, tables = tables, paragraphs = paragraphs, templates = templates, sections = subsections, categories = 0, references = references, only = only and mw.text.trim(only, 's') .. 's', noBold = noBold, noSelfLinks = true, noNonFreeFiles = true, noBehaviorSwitches = true, fixReferences = true, linkBold = true, } -- Get the excerpt itself local title = page .. '#' .. (section or '') local ok, excerpt = pcall(Transcluder.get, title, options) if not ok then return getError(excerpt) end if mw.text.trim(excerpt) == '' and not only then if section then return getError('section-empty', section) else return getError('lead-empty') end end -- Add a line break in case the excerpt starts with a table or list excerpt = '\n' .. excerpt -- If no file was found, try to excerpt one from the removed infoboxes local fileNamespaces = Transcluder.getNamespaces('File') if ((only == 'file' or only == 'files') or (not only and (files ~= '0' or not files))) and -- caller asked for files not Transcluder.matchAny(excerpt, '%[%[', fileNamespaces, ':') and -- and there are no files in Transcluder's output config.captions -- and we have the config option required to try finding files in templates then local templates = Transcluder.get(title, { only = 'templates', templates = blacklist, fixReferences = true } ) local parameters = Transcluder.getParameters(templates) local file, captions, caption for _, pair in pairs(config.captions) do file = pair[1] file = parameters[file] if file and Transcluder.matchAny(file, '^.*%.', {'[Jj][Pp][Ee]?[Gg]','[Pp][Nn][Gg]','[Gg][Ii][Ff]','[Ss][Vv][Gg]'}, '.*') then file = mw.ustring.match(file, '%[?%[?.-:([^{|]+)%]?%]?') or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs(captions) do if parameters[p] then caption = parameters[p] break end end excerpt = '[[File:' .. file .. '|thumb|' .. (caption or '') .. ']]' .. excerpt excerpt = Transcluder.removeNonFreeFiles(excerpt) break end end end -- Remove nested categories excerpt = frame:preprocess(excerpt) local categories, excerpt = Transcluder.getCategories(excerpt, options.categories) -- Add tracking categories if config.categories then local contentCategory = config.categories.content if contentCategory and mw.title.getCurrentTitle().isContentPage then excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ mw.title.getCurrentTitle().namespace ] if namespaceCategory then excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end end -- Load the styles local styles if config.styles then styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) end -- Combine and return the elements local tag1 = 'div' local tag2 = 'div' if inline then tag1 = 'span' tag2 = 'span' elseif quote then tag2 = 'blockquote' end excerpt = mw.html.create(tag1):addClass('excerpt'):wikitext(excerpt) local block = mw.html.create(tag2):addClass('excerpt-block'):addClass(class) return block:node(styles):node(hat):node(excerpt):node(more) end -- Entry points for backwards compatibility function p.lead(frame) return p.main(frame) end function p.excerpt(frame) return p.main(frame) end return p lap3h4d0iqssyn9rm7th0fm8z6vb8f0 797345 797344 2022-05-18T21:14:15Z en>Sophivorus 0 Update to latest version 797345 Scribunto text/plain local Transcluder = require('Module:Transcluder') local yesno = require('Module:Yesno') local ok, config = pcall(require, 'Module:Excerpt/config') if not ok then config = {} end local p = {} -- Helper function to get arguments local args function getArg(key, default) value = args[key] if value and mw.text.trim(value) ~= '' then return value end return default end -- Helper function to handle errors function getError(message, value) if type(message) == 'string' then message = Transcluder.getError(message, value) end if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then message:node('[[Category:' .. config.categories.errors .. ']]') end return message end -- Helper function to get localized messages function getMessage(key) local ok, TNT = pcall(require, 'Module:TNT') if not ok then return key end return TNT.format('I18n/Module:Excerpt.tab', key) end function p.main(frame) args = Transcluder.parseArgs(frame) -- Make sure the requested page exists local page = getArg(1) if not page then return getError('no-page') end local title = mw.title.new(page) if not title then return getError('no-page') end if title.isRedirect then title = title.redirectTarget end if not title.exists then return getError('page-not-found', page) end page = title.prefixedText -- Set variables local section = getArg(2, mw.ustring.match( getArg(1), '[^#]+#(.+)') ) local hat = yesno( getArg('hat', true) ) local edit = yesno( getArg('edit', true) ) local this = getArg('this') local only = getArg('only') local files = getArg('files', getArg('file', ( only == 'file' and 1 ) ) ) local lists = getArg('lists', getArg('list', ( only == 'list' and 1 ) ) ) local tables = getArg('tables', getArg('table', ( only == 'table' and 1 ) ) ) local templates = getArg('templates', getArg('template', ( only == 'template' and 1 ) ) ) local paragraphs = getArg('paragraphs', getArg('paragraph', ( only == 'paragraph' and 1 ) ) ) local references = getArg('references') local subsections = not yesno( getArg('subsections') ) local noBold = not yesno( getArg('bold') ) local inline = yesno( getArg('inline') ) local quote = yesno( getArg('quote') ) local more = yesno( getArg('more') ) local class = getArg('class') -- Build the hatnote if hat and not inline then if this then hat = this elseif quote then hat = getMessage('this') elseif only then hat = getMessage(only) else hat = getMessage('section') end hat = hat .. ' ' .. getMessage('excerpt') .. ' ' if section and not fragment then hat = hat .. '[[:' .. page .. '#' .. mw.uri.anchorEncode(section) .. '|' .. page .. ' § ' .. mw.ustring.gsub(section, '%[%[([^]|]+)|?[^]]*%]%]', '%1') .. ']].' -- remove nested links else hat = hat .. '[[:' .. page .. '|' .. page .. ']].' end if edit then hat = hat .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. title:fullUrl('action=edit') .. ' ' .. mw.message.new('editsection'):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' end if config.hat then hat = config.hat .. hat .. '}}' hat = frame:preprocess(hat) else hat = mw.html.create('div'):addClass('dablink excerpt-hat'):wikitext(hat) end else hat = nil end -- Build the "Read more" link if more and not inline then more = "'''[[" .. page .. '#' .. (section or '') .. "|" .. getMessage('more') .. "]]'''" more = mw.html.create('div'):addClass('noprint excerpt-more'):wikitext(more) else more = nil end -- Build the templates option from the config blacklist local blacklist = config.blacklist and table.concat(config.blacklist, ',') or '' if templates then if string.sub(templates, 1, 1) == '-' then templates = templates .. ',' .. blacklist end else templates = '-' .. blacklist end -- Build the options for Module:Transcluder out of the template arguments and the desired defaults local options = { files = files, lists = lists, tables = tables, paragraphs = paragraphs, templates = templates, sections = subsections, categories = 0, references = references, only = only and mw.text.trim(only, 's') .. 's', noBold = noBold, noSelfLinks = true, noNonFreeFiles = true, noBehaviorSwitches = true, fixReferences = true, linkBold = true, } -- Get the excerpt itself local title = page .. '#' .. (section or '') local ok, excerpt = pcall(Transcluder.get, title, options) if not ok then return getError(excerpt) end if mw.text.trim(excerpt) == '' and not only then if section then return getError('section-empty', section) else return getError('lead-empty') end end -- Add a line break before an after so that the parser interprets lists, tables, etc. correctly excerpt = '\n' .. excerpt .. '\n' -- If no file was found, try to excerpt one from the removed infoboxes local fileNamespaces = Transcluder.getNamespaces('File') if ((only == 'file' or only == 'files') or (not only and (files ~= '0' or not files))) and -- caller asked for files not Transcluder.matchAny(excerpt, '%[%[', fileNamespaces, ':') and -- and there are no files in Transcluder's output config.captions -- and we have the config option required to try finding files in templates then local templates = Transcluder.get(title, { only = 'templates', templates = blacklist, fixReferences = true } ) local parameters = Transcluder.getParameters(templates) local file, captions, caption for _, pair in pairs(config.captions) do file = pair[1] file = parameters[file] if file and Transcluder.matchAny(file, '^.*%.', {'[Jj][Pp][Ee]?[Gg]','[Pp][Nn][Gg]','[Gg][Ii][Ff]','[Ss][Vv][Gg]'}, '.*') then file = mw.ustring.match(file, '%[?%[?.-:([^{|]+)%]?%]?') or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs(captions) do if parameters[p] then caption = parameters[p] break end end excerpt = '[[File:' .. file .. '|thumb|' .. (caption or '') .. ']]' .. excerpt excerpt = Transcluder.removeNonFreeFiles(excerpt) break end end end -- Remove nested categories excerpt = frame:preprocess(excerpt) local categories, excerpt = Transcluder.getCategories(excerpt, options.categories) -- Add tracking categories if config.categories then local contentCategory = config.categories.content if contentCategory and mw.title.getCurrentTitle().isContentPage then excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ mw.title.getCurrentTitle().namespace ] if namespaceCategory then excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end end -- Load the styles local styles if config.styles then styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) end -- Combine and return the elements local tag1 = 'div' local tag2 = 'div' if inline then tag1 = 'span' tag2 = 'span' elseif quote then tag2 = 'blockquote' end excerpt = mw.html.create(tag1):addClass('excerpt'):wikitext(excerpt) local block = mw.html.create(tag2):addClass('excerpt-block'):addClass(class) return block:node(styles):node(hat):node(excerpt):node(more) end -- Entry points for backwards compatibility function p.lead(frame) return p.main(frame) end function p.excerpt(frame) return p.main(frame) end return p 2dmmevva564fdsk6vpqsgo0ntl9basg 797346 797345 2022-06-01T22:04:00Z en>Sophivorus 0 Update to 1.1, see talk page 797346 Scribunto text/plain -- Module:Excerpt implements the Excerpt template -- Documentation https://www.mediawiki.org/wiki/Module:Excerpt -- By User:Sophivorus, User:Certes & others -- Version 1.1 -- License CC-BY-SA-4.0 local Transcluder = require('Module:Transcluder') local yesno = require('Module:Yesno') local ok, config = pcall(require, 'Module:Excerpt/config') if not ok then config = {} end local p = {} -- Helper function to get arguments local args function getArg(key, default) value = args[key] if value and mw.text.trim(value) ~= '' then return value end return default end -- Helper function to handle errors function getError(message, value) if type(message) == 'string' then message = Transcluder.getError(message, value) end if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then message:node('[[Category:' .. config.categories.errors .. ']]') end return message end -- Helper function to get localized messages function getMessage(key) local ok, TNT = pcall(require, 'Module:TNT') if not ok then return key end return TNT.format('I18n/Module:Excerpt.tab', key) end function p.main(frame) args = Transcluder.parseArgs(frame) -- Make sure the requested page exists local page = getArg(1) if not page then return getError('no-page') end local title = mw.title.new(page) if not title then return getError('no-page') end if title.isRedirect then title = title.redirectTarget end if not title.exists then return getError('page-not-found', page) end page = title.prefixedText -- Set variables from the template parameters local section = getArg(2, mw.ustring.match( getArg(1), '[^#]+#(.+)') ) local hat = yesno( getArg('hat', true) ) local edit = yesno( getArg('edit', true) ) local this = getArg('this') local only = getArg('only') local files = getArg('files', getArg('file', ( only == 'file' and 1 ) ) ) local lists = getArg('lists', getArg('list', ( only == 'list' and 1 ) ) ) local tables = getArg('tables', getArg('table', ( only == 'table' and 1 ) ) ) local templates = getArg('templates', getArg('template', ( only == 'template' and 1 ) ) ) local paragraphs = getArg('paragraphs', getArg('paragraph', ( only == 'paragraph' and 1 ) ) ) local references = getArg('references') local subsections = not yesno( getArg('subsections') ) local noBold = not yesno( getArg('bold') ) local freefiles = yesno( getArg('freefiles') ) local inline = yesno( getArg('inline') ) local quote = yesno( getArg('quote') ) local more = yesno( getArg('more') ) local class = getArg('class') -- Build the hatnote if hat and not inline then if this then hat = this elseif quote then hat = getMessage('this') elseif only then hat = getMessage(only) else hat = getMessage('section') end hat = hat .. ' ' .. getMessage('excerpt') .. ' ' if section and not fragment then hat = hat .. '[[:' .. page .. '#' .. mw.uri.anchorEncode(section) .. '|' .. page .. ' § ' .. mw.ustring.gsub(section, '%[%[([^]|]+)|?[^]]*%]%]', '%1') .. ']].' -- remove nested links else hat = hat .. '[[:' .. page .. '|' .. page .. ']].' end if edit then hat = hat .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. title:fullUrl('action=edit') .. ' ' .. mw.message.new('editsection'):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' end if config.hat then hat = config.hat .. hat .. '}}' hat = frame:preprocess(hat) else hat = mw.html.create('div'):addClass('dablink excerpt-hat'):wikitext(hat) end else hat = nil end -- Build the "Read more" link if more and not inline then more = "'''[[" .. page .. '#' .. (section or '') .. "|" .. getMessage('more') .. "]]'''" more = mw.html.create('div'):addClass('noprint excerpt-more'):wikitext(more) else more = nil end -- Build the options for Module:Transcluder out of the template parameters and the desired defaults local options = { files = files, lists = lists, tables = tables, paragraphs = paragraphs, sections = subsections, categories = 0, references = references, only = only and mw.text.trim(only, 's') .. 's', noBold = noBold, noSelfLinks = true, noNonFreeFiles = freefiles, noBehaviorSwitches = true, fixReferences = true, linkBold = true, } -- Get the excerpt itself local title = page .. '#' .. (section or '') local ok, excerpt = pcall(Transcluder.get, title, options) if not ok then return getError(excerpt) end if mw.text.trim(excerpt) == '' and not only then if section then return getError('section-empty', section) else return getError('lead-empty') end end -- If no file was found, try to get one from the infobox local fileNamespaces = Transcluder.getNamespaces('File') if ((only == 'file' or only == 'files') or (not only and (files ~= '0' or not files))) and -- caller asked for files not Transcluder.matchAny(excerpt, '%[%[', fileNamespaces, ':') and -- and there are no files in Transcluder's output config.captions -- and we have the config option required to try finding files in templates then -- We cannot distinguish the infobox from the other templates so we search them all local infobox = Transcluder.getTemplates(excerpt); infobox = table.concat(infobox) local parameters = Transcluder.getParameters(infobox) local file, captions, caption for _, pair in pairs(config.captions) do file = pair[1] file = parameters[file] if file and Transcluder.matchAny(file, '^.*%.', {'[Jj][Pp][Ee]?[Gg]','[Pp][Nn][Gg]','[Gg][Ii][Ff]','[Ss][Vv][Gg]'}, '.*') then file = mw.ustring.match(file, '%[?%[?.-:([^{|]+)%]?%]?') or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs(captions) do if parameters[p] then caption = parameters[p] break end end excerpt = '[[File:' .. file .. '|thumb|' .. (caption or '') .. ']]' .. excerpt if ( freefiles ) then excerpt = Transcluder.removeNonFreeFiles(excerpt) end break end end end -- Unlike other elements, templates are filtered here -- because we had to search the infoboxes for files local trash if only and (only == 'template' or only == 'templates') then trash, excerpt = Transcluder.getTemplates(excerpt, templates); else -- Remove blacklisted templates local blacklist = config.blacklist and table.concat(config.blacklist, ',') or '' if templates then if string.sub(templates, 1, 1) == '-' then blacklist = templates .. ',' .. blacklist end else blacklist = '-' .. blacklist end trash, excerpt = Transcluder.getTemplates(excerpt, blacklist); end -- Remove extra line breaks but leave one before and after so the parser interprets lists, tables, etc. correctly excerpt = mw.text.trim(excerpt) excerpt = string.gsub(excerpt, '\n\n\n+', '\n\n') excerpt = '\n' .. excerpt .. '\n' -- Remove nested categories excerpt = frame:preprocess(excerpt) local categories, excerpt = Transcluder.getCategories(excerpt, options.categories) -- Add tracking categories if config.categories then local contentCategory = config.categories.content if contentCategory and mw.title.getCurrentTitle().isContentPage then excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ mw.title.getCurrentTitle().namespace ] if namespaceCategory then excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end end -- Load the styles local styles if config.styles then styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) end -- Combine and return the elements local tag1 = 'div' local tag2 = 'div' if inline then tag1 = 'span' tag2 = 'span' elseif quote then tag2 = 'blockquote' end excerpt = mw.html.create(tag1):addClass('excerpt'):wikitext(excerpt) local block = mw.html.create(tag2):addClass('excerpt-block'):addClass(class) return block:node(styles):node(hat):node(excerpt):node(more) end -- Entry points for backwards compatibility function p.lead(frame) return p.main(frame) end function p.excerpt(frame) return p.main(frame) end return p 7an5kmuz709b95mdgh2qctd1ijuoev1 797347 797346 2022-06-05T13:43:46Z en>Sophivorus 0 Update to 1.2 797347 Scribunto text/plain -- Module:Excerpt implements the Excerpt template -- Documentation https://www.mediawiki.org/wiki/Module:Excerpt -- By User:Sophivorus, User:Certes & others -- Version 1.2 -- License CC-BY-SA-4.0 local Transcluder = require('Module:Transcluder') local yesno = require('Module:Yesno') local ok, config = pcall(require, 'Module:Excerpt/config') if not ok then config = {} end local p = {} -- Helper function to get arguments local args function getArg(key, default) value = args[key] if value and mw.text.trim(value) ~= '' then return value end return default end -- Helper function to handle errors function getError(message, value) if type(message) == 'string' then message = Transcluder.getError(message, value) end if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then message:node('[[Category:' .. config.categories.errors .. ']]') end return message end -- Helper function to get localized messages function getMessage(key) local ok, TNT = pcall(require, 'Module:TNT') if not ok then return key end return TNT.format('I18n/Module:Excerpt.tab', key) end function p.main(frame) args = Transcluder.parseArgs(frame) -- Make sure the requested page exists local page = getArg(1) if not page then return getError('no-page') end local title = mw.title.new(page) if not title then return getError('no-page') end if title.isRedirect then title = title.redirectTarget end if not title.exists then return getError('page-not-found', page) end page = title.prefixedText -- Set variables from the template parameters local section = getArg(2, mw.ustring.match( getArg(1), '[^#]+#(.+)') ) local hat = yesno( getArg('hat', true) ) local edit = yesno( getArg('edit', true) ) local this = getArg('this') local only = getArg('only') local files = getArg('files', getArg('file', ( only == 'file' and 1 ) ) ) local lists = getArg('lists', getArg('list', ( only == 'list' and 1 ) ) ) local tables = getArg('tables', getArg('table', ( only == 'table' and 1 ) ) ) local templates = getArg('templates', getArg('template', ( only == 'template' and 1 ) ) ) local paragraphs = getArg('paragraphs', getArg('paragraph', ( only == 'paragraph' and 1 ) ) ) local references = getArg('references') local subsections = not yesno( getArg('subsections') ) local noBold = not yesno( getArg('bold') ) local freefiles = yesno( getArg('freefiles') ) local inline = yesno( getArg('inline') ) local quote = yesno( getArg('quote') ) local more = yesno( getArg('more') ) local class = getArg('class') -- Build the hatnote if hat and not inline then if this then hat = this elseif quote then hat = getMessage('this') elseif only then hat = getMessage(only) else hat = getMessage('section') end hat = hat .. ' ' .. getMessage('excerpt') .. ' ' if section and not fragment then hat = hat .. '[[:' .. page .. '#' .. mw.uri.anchorEncode(section) .. '|' .. page .. ' § ' .. mw.ustring.gsub(section, '%[%[([^]|]+)|?[^]]*%]%]', '%1') .. ']].' -- remove nested links else hat = hat .. '[[:' .. page .. '|' .. page .. ']].' end if edit then hat = hat .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. title:fullUrl('action=edit') .. ' ' .. mw.message.new('editsection'):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' end if config.hat then hat = config.hat .. hat .. '}}' hat = frame:preprocess(hat) else hat = mw.html.create('div'):addClass('dablink excerpt-hat'):wikitext(hat) end else hat = nil end -- Build the "Read more" link if more and not inline then more = "'''[[" .. page .. '#' .. (section or '') .. "|" .. getMessage('more') .. "]]'''" more = mw.html.create('div'):addClass('noprint excerpt-more'):wikitext(more) else more = nil end -- Build the options for Module:Transcluder out of the template parameters and the desired defaults local options = { files = files, lists = lists, tables = tables, paragraphs = paragraphs, sections = subsections, categories = 0, references = references, only = only and mw.text.trim(only, 's') .. 's', noBold = noBold, noSelfLinks = true, noNonFreeFiles = freefiles, noBehaviorSwitches = true, fixReferences = true, linkBold = true, } -- Get the excerpt itself local title = page .. '#' .. (section or '') local ok, excerpt = pcall(Transcluder.get, title, options) if not ok then return getError(excerpt) end if mw.text.trim(excerpt) == '' and not only then if section then return getError('section-empty', section) else return getError('lead-empty') end end -- If no file was found, try to get one from the infobox local fileNamespaces = Transcluder.getNamespaces('File') if ((only == 'file' or only == 'files') or (not only and (files ~= '0' or not files))) and -- caller asked for files not Transcluder.matchAny(excerpt, '%[%[', fileNamespaces, ':') and -- and there are no files in Transcluder's output config.captions -- and we have the config option required to try finding files in templates then -- We cannot distinguish the infobox from the other templates so we search them all local infobox = Transcluder.getTemplates(excerpt); infobox = table.concat(infobox) local parameters = Transcluder.getParameters(infobox) local file, captions, caption for _, pair in pairs(config.captions) do file = pair[1] file = parameters[file] if file and Transcluder.matchAny(file, '^.*%.', {'[Jj][Pp][Ee]?[Gg]','[Pp][Nn][Gg]','[Gg][Ii][Ff]','[Ss][Vv][Gg]'}, '.*') then file = mw.ustring.match(file, '%[?%[?.-:([^{|]+)%]?%]?') or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs(captions) do if parameters[p] then caption = parameters[p] break end end excerpt = '[[File:' .. file .. '|thumb|' .. (caption or '') .. ']]' .. excerpt if ( freefiles ) then excerpt = Transcluder.removeNonFreeFiles(excerpt) end break end end end -- Unlike other elements, templates are filtered here -- because we had to search the infoboxes for files local trash if only and (only == 'template' or only == 'templates') then trash, excerpt = Transcluder.getTemplates(excerpt, templates); else -- Remove blacklisted templates local blacklist = config.blacklist and table.concat(config.blacklist, ',') or '' if templates then if string.sub(templates, 1, 1) == '-' then --Unwanted templates. Append to blacklist blacklist = templates .. ',' .. blacklist else --Wanted templates. Replaces blacklist and acts as whitelist blacklist = templates end else blacklist = '-' .. blacklist end trash, excerpt = Transcluder.getTemplates(excerpt, blacklist); end -- Remove extra line breaks but leave one before and after so the parser interprets lists, tables, etc. correctly excerpt = mw.text.trim(excerpt) excerpt = string.gsub(excerpt, '\n\n\n+', '\n\n') excerpt = '\n' .. excerpt .. '\n' -- Remove nested categories excerpt = frame:preprocess(excerpt) local categories, excerpt = Transcluder.getCategories(excerpt, options.categories) -- Add tracking categories if config.categories then local contentCategory = config.categories.content if contentCategory and mw.title.getCurrentTitle().isContentPage then excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ mw.title.getCurrentTitle().namespace ] if namespaceCategory then excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end end -- Load the styles local styles if config.styles then styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) end -- Combine and return the elements local tag1 = 'div' local tag2 = 'div' if inline then tag1 = 'span' tag2 = 'span' elseif quote then tag2 = 'blockquote' end excerpt = mw.html.create(tag1):addClass('excerpt'):wikitext(excerpt) local block = mw.html.create(tag2):addClass('excerpt-block'):addClass(class) return block:node(styles):node(hat):node(excerpt):node(more) end -- Entry points for backwards compatibility function p.lead(frame) return p.main(frame) end function p.excerpt(frame) return p.main(frame) end return p sgzuf1kkpk56x8im7uw6ztnfi4wkw6e 797348 797347 2022-08-05T12:50:20Z en>Sophivorus 0 Update to 1.2.3, fixes lint errors (see talk page), global scope issue and code style changes 797348 Scribunto text/plain -- Module:Excerpt implements the Excerpt template -- Documentation https://www.mediawiki.org/wiki/Module:Excerpt -- By User:Sophivorus, User:Certes & others -- Version 1.2.3 -- License CC-BY-SA-4.0 local Transcluder = require( 'Module:Transcluder' ) local yesno = require( 'Module:Yesno' ) local ok, config = pcall( require, 'Module:Excerpt/config' ) if not ok then config = {} end local p = {} -- Helper function to get arguments local args function getArg( key, default ) local value = args[ key ] if value and mw.text.trim( value ) ~= '' then return value end return default end -- Helper function to handle errors function getError( message, value ) if type( message ) == 'string' then message = Transcluder.getError( message, value ) end if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then message:node( '[[Category:' .. config.categories.errors .. ']]' ) end return message end -- Helper function to get localized messages function getMessage( key ) local ok, TNT = pcall( require, 'Module:TNT' ) if not ok then return key end return TNT.format( 'I18n/Module:Excerpt.tab', key ) end function p.main( frame ) args = Transcluder.parseArgs( frame ) -- Make sure the requested page exists local page = getArg( 1 ) if not page then return getError( 'no-page' ) end local title = mw.title.new(page) if not title then return getError( 'no-page' ) end if title.isRedirect then title = title.redirectTarget end if not title.exists then return getError( 'page-not-found', page ) end page = title.prefixedText -- Set variables from the template parameters local section = getArg( 2, mw.ustring.match( getArg( 1 ), '[^#]+#(.+)' ) ) local hat = yesno( getArg( 'hat', true ) ) local edit = yesno( getArg( 'edit', true ) ) local this = getArg( 'this' ) local only = getArg( 'only' ) local files = getArg( 'files', getArg( 'file', ( only == 'file' and 1 ) ) ) local lists = getArg( 'lists', getArg( 'list', ( only == 'list' and 1 ) ) ) local tables = getArg( 'tables', getArg( 'table', ( only == 'table' and 1 ) ) ) local templates = getArg( 'templates', getArg( 'template', ( only == 'template' and 1 ) ) ) local paragraphs = getArg( 'paragraphs', getArg( 'paragraph', ( only == 'paragraph' and 1 ) ) ) local references = getArg( 'references' ) local subsections = not yesno( getArg( 'subsections' ) ) local noBold = not yesno( getArg( 'bold' ) ) local freefiles = yesno( getArg( 'freefiles' ) ) local inline = yesno( getArg( 'inline' ) ) local quote = yesno( getArg( 'quote' ) ) local more = yesno( getArg( 'more' ) ) local class = getArg( 'class' ) -- Build the hatnote if hat and not inline then if this then hat = this elseif quote then hat = getMessage( 'this' ) elseif only then hat = getMessage( only ) else hat = getMessage( 'section' ) end hat = hat .. ' ' .. getMessage( 'excerpt' ) .. ' ' if section and not fragment then hat = hat .. '[[:' .. page .. '#' .. mw.uri.anchorEncode( section ) .. '|' .. page .. ' § ' .. mw.ustring.gsub( section, '%[%[([^]|]+)|?[^]]*%]%]', '%1' ) .. ']].' -- remove nested links else hat = hat .. '[[:' .. page .. '|' .. page .. ']].' end if edit then hat = hat .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. title:fullUrl( 'action=edit' ) .. ' ' .. mw.message.new( 'editsection' ):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' end if config.hat then hat = config.hat .. hat .. '}}' hat = frame:preprocess( hat ) else hat = mw.html.create( 'div' ):addClass( 'dablink excerpt-hat' ):wikitext( hat ) end else hat = nil end -- Build the "Read more" link if more and not inline then more = "'''[[" .. page .. '#' .. ( section or '' ) .. "|" .. getMessage( 'more' ) .. "]]'''" more = mw.html.create( 'div' ):addClass( 'noprint excerpt-more' ):wikitext( more ) else more = nil end -- Build the options for Module:Transcluder out of the template parameters and the desired defaults local options = { files = files, lists = lists, tables = tables, paragraphs = paragraphs, sections = subsections, categories = 0, references = references, only = only and mw.text.trim( only, 's' ) .. 's', noBold = noBold, noSelfLinks = true, noNonFreeFiles = freefiles, noBehaviorSwitches = true, fixReferences = true, linkBold = true, } -- Get the excerpt itself local title = page .. '#' .. ( section or '' ) local ok, excerpt = pcall( Transcluder.get, title, options ) if not ok then return getError( excerpt ) end if mw.text.trim( excerpt ) == '' and not only then if section then return getError( 'section-empty', section ) else return getError( 'lead-empty' ) end end -- If no file was found, try to get one from the infobox local fileNamespaces = Transcluder.getNamespaces( 'File' ) if ( ( only == 'file' or only == 'files' ) or ( not only and ( files ~= '0' or not files ) ) ) and -- caller asked for files not Transcluder.matchAny( excerpt, '%[%[', fileNamespaces, ':' ) and -- and there are no files in Transcluder's output config.captions -- and we have the config option required to try finding files in templates then -- We cannot distinguish the infobox from the other templates so we search them all local infobox = Transcluder.getTemplates( excerpt ); infobox = table.concat( infobox ) local parameters = Transcluder.getParameters( infobox ) local file, captions, caption for _, pair in pairs( config.captions ) do file = pair[1] file = parameters[file] if file and Transcluder.matchAny( file, '^.*%.', { '[Jj][Pp][Ee]?[Gg]', '[Pp][Nn][Gg]', '[Gg][Ii][Ff]', '[Ss][Vv][Gg]' }, '.*' ) then file = mw.ustring.match( file, '%[?%[?.-:([^{|]+)%]?%]?' ) or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs( captions ) do if parameters[ p ] then caption = parameters[ p ] break end end excerpt = '[[File:' .. file .. '|thumb|' .. ( caption or '' ) .. ']]' .. excerpt if ( freefiles ) then excerpt = Transcluder.removeNonFreeFiles( excerpt ) end break end end end -- Unlike other elements, templates are filtered here -- because we had to search the infoboxes for files local trash if only and ( only == 'template' or only == 'templates' ) then trash, excerpt = Transcluder.getTemplates( excerpt, templates ); else -- Remove blacklisted templates local blacklist = config.blacklist and table.concat( config.blacklist, ',' ) or '' if templates then if string.sub( templates, 1, 1 ) == '-' then --Unwanted templates. Append to blacklist blacklist = templates .. ',' .. blacklist else --Wanted templates. Replaces blacklist and acts as whitelist blacklist = templates end else blacklist = '-' .. blacklist end trash, excerpt = Transcluder.getTemplates( excerpt, blacklist ); end -- Remove extra line breaks but leave one before and after so the parser interprets lists, tables, etc. correctly excerpt = mw.text.trim( excerpt ) excerpt = string.gsub( excerpt, '\n\n\n+', '\n\n' ) excerpt = '\n' .. excerpt .. '\n' -- Remove nested categories excerpt = frame:preprocess( excerpt ) local categories, excerpt = Transcluder.getCategories( excerpt, options.categories ) -- Add tracking categories if config.categories then local contentCategory = config.categories.content if contentCategory and mw.title.getCurrentTitle().isContentPage then excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ mw.title.getCurrentTitle().namespace ] if namespaceCategory then excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end end -- Load the styles local styles if config.styles then styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) end -- Combine and return the elements local tag1 = 'div' local tag2 = 'div' if quote then tag2 = 'blockquote' end excerpt = mw.html.create( tag1 ):addClass( 'excerpt' ):wikitext( excerpt ) if inline then excerpt = excerpt:css( 'display', 'inline' ) end local block = mw.html.create( tag2 ):addClass( 'excerpt-block' ):addClass( class ) if inline then block = block:css( 'display', 'inline' ) end return block:node( styles ):node( hat ):node( excerpt ):node( more ) end -- Entry points for backwards compatibility function p.lead( frame ) return p.main( frame ) end function p.excerpt( frame ) return p.main( frame ) end return p 5h3pvy13bxtuovxpxbtkayehtaqlw8g 797349 797348 2022-08-16T01:48:04Z en>JJMC89 0 our license is 3.0 797349 Scribunto text/plain -- Module:Excerpt implements the Excerpt template -- Documentation https://www.mediawiki.org/wiki/Module:Excerpt -- By User:Sophivorus, User:Certes & others -- Version 1.2.3 -- License CC BY-SA-3.0 local Transcluder = require( 'Module:Transcluder' ) local yesno = require( 'Module:Yesno' ) local ok, config = pcall( require, 'Module:Excerpt/config' ) if not ok then config = {} end local p = {} -- Helper function to get arguments local args function getArg( key, default ) local value = args[ key ] if value and mw.text.trim( value ) ~= '' then return value end return default end -- Helper function to handle errors function getError( message, value ) if type( message ) == 'string' then message = Transcluder.getError( message, value ) end if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then message:node( '[[Category:' .. config.categories.errors .. ']]' ) end return message end -- Helper function to get localized messages function getMessage( key ) local ok, TNT = pcall( require, 'Module:TNT' ) if not ok then return key end return TNT.format( 'I18n/Module:Excerpt.tab', key ) end function p.main( frame ) args = Transcluder.parseArgs( frame ) -- Make sure the requested page exists local page = getArg( 1 ) if not page then return getError( 'no-page' ) end local title = mw.title.new(page) if not title then return getError( 'no-page' ) end if title.isRedirect then title = title.redirectTarget end if not title.exists then return getError( 'page-not-found', page ) end page = title.prefixedText -- Set variables from the template parameters local section = getArg( 2, mw.ustring.match( getArg( 1 ), '[^#]+#(.+)' ) ) local hat = yesno( getArg( 'hat', true ) ) local edit = yesno( getArg( 'edit', true ) ) local this = getArg( 'this' ) local only = getArg( 'only' ) local files = getArg( 'files', getArg( 'file', ( only == 'file' and 1 ) ) ) local lists = getArg( 'lists', getArg( 'list', ( only == 'list' and 1 ) ) ) local tables = getArg( 'tables', getArg( 'table', ( only == 'table' and 1 ) ) ) local templates = getArg( 'templates', getArg( 'template', ( only == 'template' and 1 ) ) ) local paragraphs = getArg( 'paragraphs', getArg( 'paragraph', ( only == 'paragraph' and 1 ) ) ) local references = getArg( 'references' ) local subsections = not yesno( getArg( 'subsections' ) ) local noBold = not yesno( getArg( 'bold' ) ) local freefiles = yesno( getArg( 'freefiles' ) ) local inline = yesno( getArg( 'inline' ) ) local quote = yesno( getArg( 'quote' ) ) local more = yesno( getArg( 'more' ) ) local class = getArg( 'class' ) -- Build the hatnote if hat and not inline then if this then hat = this elseif quote then hat = getMessage( 'this' ) elseif only then hat = getMessage( only ) else hat = getMessage( 'section' ) end hat = hat .. ' ' .. getMessage( 'excerpt' ) .. ' ' if section and not fragment then hat = hat .. '[[:' .. page .. '#' .. mw.uri.anchorEncode( section ) .. '|' .. page .. ' § ' .. mw.ustring.gsub( section, '%[%[([^]|]+)|?[^]]*%]%]', '%1' ) .. ']].' -- remove nested links else hat = hat .. '[[:' .. page .. '|' .. page .. ']].' end if edit then hat = hat .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. title:fullUrl( 'action=edit' ) .. ' ' .. mw.message.new( 'editsection' ):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' end if config.hat then hat = config.hat .. hat .. '}}' hat = frame:preprocess( hat ) else hat = mw.html.create( 'div' ):addClass( 'dablink excerpt-hat' ):wikitext( hat ) end else hat = nil end -- Build the "Read more" link if more and not inline then more = "'''[[" .. page .. '#' .. ( section or '' ) .. "|" .. getMessage( 'more' ) .. "]]'''" more = mw.html.create( 'div' ):addClass( 'noprint excerpt-more' ):wikitext( more ) else more = nil end -- Build the options for Module:Transcluder out of the template parameters and the desired defaults local options = { files = files, lists = lists, tables = tables, paragraphs = paragraphs, sections = subsections, categories = 0, references = references, only = only and mw.text.trim( only, 's' ) .. 's', noBold = noBold, noSelfLinks = true, noNonFreeFiles = freefiles, noBehaviorSwitches = true, fixReferences = true, linkBold = true, } -- Get the excerpt itself local title = page .. '#' .. ( section or '' ) local ok, excerpt = pcall( Transcluder.get, title, options ) if not ok then return getError( excerpt ) end if mw.text.trim( excerpt ) == '' and not only then if section then return getError( 'section-empty', section ) else return getError( 'lead-empty' ) end end -- If no file was found, try to get one from the infobox local fileNamespaces = Transcluder.getNamespaces( 'File' ) if ( ( only == 'file' or only == 'files' ) or ( not only and ( files ~= '0' or not files ) ) ) and -- caller asked for files not Transcluder.matchAny( excerpt, '%[%[', fileNamespaces, ':' ) and -- and there are no files in Transcluder's output config.captions -- and we have the config option required to try finding files in templates then -- We cannot distinguish the infobox from the other templates so we search them all local infobox = Transcluder.getTemplates( excerpt ); infobox = table.concat( infobox ) local parameters = Transcluder.getParameters( infobox ) local file, captions, caption for _, pair in pairs( config.captions ) do file = pair[1] file = parameters[file] if file and Transcluder.matchAny( file, '^.*%.', { '[Jj][Pp][Ee]?[Gg]', '[Pp][Nn][Gg]', '[Gg][Ii][Ff]', '[Ss][Vv][Gg]' }, '.*' ) then file = mw.ustring.match( file, '%[?%[?.-:([^{|]+)%]?%]?' ) or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs( captions ) do if parameters[ p ] then caption = parameters[ p ] break end end excerpt = '[[File:' .. file .. '|thumb|' .. ( caption or '' ) .. ']]' .. excerpt if ( freefiles ) then excerpt = Transcluder.removeNonFreeFiles( excerpt ) end break end end end -- Unlike other elements, templates are filtered here -- because we had to search the infoboxes for files local trash if only and ( only == 'template' or only == 'templates' ) then trash, excerpt = Transcluder.getTemplates( excerpt, templates ); else -- Remove blacklisted templates local blacklist = config.blacklist and table.concat( config.blacklist, ',' ) or '' if templates then if string.sub( templates, 1, 1 ) == '-' then --Unwanted templates. Append to blacklist blacklist = templates .. ',' .. blacklist else --Wanted templates. Replaces blacklist and acts as whitelist blacklist = templates end else blacklist = '-' .. blacklist end trash, excerpt = Transcluder.getTemplates( excerpt, blacklist ); end -- Remove extra line breaks but leave one before and after so the parser interprets lists, tables, etc. correctly excerpt = mw.text.trim( excerpt ) excerpt = string.gsub( excerpt, '\n\n\n+', '\n\n' ) excerpt = '\n' .. excerpt .. '\n' -- Remove nested categories excerpt = frame:preprocess( excerpt ) local categories, excerpt = Transcluder.getCategories( excerpt, options.categories ) -- Add tracking categories if config.categories then local contentCategory = config.categories.content if contentCategory and mw.title.getCurrentTitle().isContentPage then excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ mw.title.getCurrentTitle().namespace ] if namespaceCategory then excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end end -- Load the styles local styles if config.styles then styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) end -- Combine and return the elements local tag1 = 'div' local tag2 = 'div' if quote then tag2 = 'blockquote' end excerpt = mw.html.create( tag1 ):addClass( 'excerpt' ):wikitext( excerpt ) if inline then excerpt = excerpt:css( 'display', 'inline' ) end local block = mw.html.create( tag2 ):addClass( 'excerpt-block' ):addClass( class ) if inline then block = block:css( 'display', 'inline' ) end return block:node( styles ):node( hat ):node( excerpt ):node( more ) end -- Entry points for backwards compatibility function p.lead( frame ) return p.main( frame ) end function p.excerpt( frame ) return p.main( frame ) end return p 8jqii8vrrp2c207nwoddkpvp5mqqfge 797350 797349 2022-09-30T14:39:16Z en>Sophivorus 0 Update to 1.5 (non free files are now removed by default, and inline excerpts now use no tags rather than <span> tags) 797350 Scribunto text/plain -- Module:Excerpt implements the Excerpt template -- Documentation https://www.mediawiki.org/wiki/Module:Excerpt -- By User:Sophivorus, User:Certes & others -- Version 1.5 -- License CC BY-SA-3.0 local Transcluder = require( 'Module:Transcluder' ) local yesno = require( 'Module:Yesno' ) local ok, config = pcall( require, 'Module:Excerpt/config' ) if not ok then config = {} end local p = {} -- Helper function to get arguments local args function getArg( key, default ) local value = args[ key ] if value and mw.text.trim( value ) ~= '' then return value end return default end -- Helper function to handle errors function getError( message, value ) if type( message ) == 'string' then message = Transcluder.getError( message, value ) end if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then message:node( '[[Category:' .. config.categories.errors .. ']]' ) end return message end -- Helper function to get localized messages function getMessage( key ) local ok, TNT = pcall( require, 'Module:TNT' ) if not ok then return key end return TNT.format( 'I18n/Module:Excerpt.tab', key ) end function p.main( frame ) args = Transcluder.parseArgs( frame ) -- Make sure the requested page exists local page = getArg( 1 ) if not page then return getError( 'no-page' ) end local title = mw.title.new(page) if not title then return getError( 'no-page' ) end if title.isRedirect then title = title.redirectTarget end if not title.exists then return getError( 'page-not-found', page ) end page = title.prefixedText -- Set variables from the template parameters local section = getArg( 2, mw.ustring.match( getArg( 1 ), '[^#]+#(.+)' ) ) local hat = yesno( getArg( 'hat', true ) ) local edit = yesno( getArg( 'edit', true ) ) local this = getArg( 'this' ) local only = getArg( 'only' ) local files = getArg( 'files', getArg( 'file', ( only == 'file' and 1 ) ) ) local lists = getArg( 'lists', getArg( 'list', ( only == 'list' and 1 ) ) ) local tables = getArg( 'tables', getArg( 'table', ( only == 'table' and 1 ) ) ) local templates = getArg( 'templates', getArg( 'template', ( only == 'template' and 1 ) ) ) local paragraphs = getArg( 'paragraphs', getArg( 'paragraph', ( only == 'paragraph' and 1 ) ) ) local references = getArg( 'references' ) local subsections = not yesno( getArg( 'subsections' ) ) local noLinks = not yesno( getArg( 'links', true ) ) local noBold = not yesno( getArg( 'bold' ) ) local onlyFreeFiles = yesno( getArg( 'onlyfreefiles', true ) ) local inline = yesno( getArg( 'inline' ) ) local quote = yesno( getArg( 'quote' ) ) local more = yesno( getArg( 'more' ) ) local class = getArg( 'class' ) -- Build the hatnote if hat and not inline then if this then hat = this elseif quote then hat = getMessage( 'this' ) elseif only then hat = getMessage( only ) else hat = getMessage( 'section' ) end hat = hat .. ' ' .. getMessage( 'excerpt' ) .. ' ' if section and not fragment then hat = hat .. '[[:' .. page .. '#' .. mw.uri.anchorEncode( section ) .. '|' .. page .. ' § ' .. mw.ustring.gsub( section, '%[%[([^]|]+)|?[^]]*%]%]', '%1' ) .. ']].' -- remove nested links else hat = hat .. '[[:' .. page .. '|' .. page .. ']].' end if edit then hat = hat .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. title:fullUrl( 'action=edit' ) .. ' ' .. mw.message.new( 'editsection' ):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' end if config.hat then hat = config.hat .. hat .. '}}' hat = frame:preprocess( hat ) else hat = mw.html.create( 'div' ):addClass( 'dablink excerpt-hat' ):wikitext( hat ) end else hat = nil end -- Build the "Read more" link if more and not inline then more = "'''[[" .. page .. '#' .. ( section or '' ) .. "|" .. getMessage( 'more' ) .. "]]'''" more = mw.html.create( 'div' ):addClass( 'noprint excerpt-more' ):wikitext( more ) else more = nil end -- Build the options for Module:Transcluder out of the template parameters and the desired defaults local options = { files = files, lists = lists, tables = tables, paragraphs = paragraphs, sections = subsections, categories = 0, references = references, only = only and mw.text.trim( only, 's' ) .. 's', noLinks = noLinks, noBold = noBold, noSelfLinks = true, noNonFreeFiles = onlyFreeFiles, noBehaviorSwitches = true, fixReferences = true, linkBold = true, } -- Get the excerpt itself local title = page .. '#' .. ( section or '' ) local ok, excerpt = pcall( Transcluder.get, title, options ) if not ok then return getError( excerpt ) end if mw.text.trim( excerpt ) == '' and not only then if section then return getError( 'section-empty', section ) else return getError( 'lead-empty' ) end end -- If no file was found, try to get one from the infobox local fileNamespaces = Transcluder.getNamespaces( 'File' ) if ( ( only == 'file' or only == 'files' ) or ( not only and ( files ~= '0' or not files ) ) ) and -- caller asked for files not Transcluder.matchAny( excerpt, '%[%[', fileNamespaces, ':' ) and -- and there are no files in Transcluder's output config.captions -- and we have the config option required to try finding files in templates then -- We cannot distinguish the infobox from the other templates so we search them all local infobox = Transcluder.getTemplates( excerpt ); infobox = table.concat( infobox ) local parameters = Transcluder.getParameters( infobox ) local file, captions, caption for _, pair in pairs( config.captions ) do file = pair[1] file = parameters[file] if file and Transcluder.matchAny( file, '^.*%.', { '[Jj][Pp][Ee]?[Gg]', '[Pp][Nn][Gg]', '[Gg][Ii][Ff]', '[Ss][Vv][Gg]' }, '.*' ) then file = mw.ustring.match( file, '%[?%[?.-:([^{|]+)%]?%]?' ) or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs( captions ) do if parameters[ p ] then caption = parameters[ p ] break end end excerpt = '[[File:' .. file .. '|thumb|' .. ( caption or '' ) .. ']]' .. excerpt if ( onlyFreeFiles ) then excerpt = Transcluder.removeNonFreeFiles( excerpt ) end break end end end -- Unlike other elements, templates are filtered here -- because we had to search the infoboxes for files local trash if only and ( only == 'template' or only == 'templates' ) then trash, excerpt = Transcluder.getTemplates( excerpt, templates ); else -- Remove blacklisted templates local blacklist = config.blacklist and table.concat( config.blacklist, ',' ) or '' if templates then if string.sub( templates, 1, 1 ) == '-' then --Unwanted templates. Append to blacklist blacklist = templates .. ',' .. blacklist else --Wanted templates. Replaces blacklist and acts as whitelist blacklist = templates end else blacklist = '-' .. blacklist end trash, excerpt = Transcluder.getTemplates( excerpt, blacklist ); end -- Remove extra line breaks but leave one before and after so the parser interprets lists, tables, etc. correctly excerpt = mw.text.trim( excerpt ) excerpt = string.gsub( excerpt, '\n\n\n+', '\n\n' ) excerpt = '\n' .. excerpt .. '\n' -- Remove nested categories excerpt = frame:preprocess( excerpt ) local categories, excerpt = Transcluder.getCategories( excerpt, options.categories ) -- Add tracking categories if config.categories then local contentCategory = config.categories.content if contentCategory and mw.title.getCurrentTitle().isContentPage then excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ mw.title.getCurrentTitle().namespace ] if namespaceCategory then excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end end -- Load the styles local styles if config.styles then styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) end -- Combine and return the elements if inline then return mw.text.trim( excerpt ) end local tag = 'div' if quote then tag = 'blockquote' end excerpt = mw.html.create( 'div' ):addClass( 'excerpt' ):wikitext( excerpt ) local block = mw.html.create( tag ):addClass( 'excerpt-block' ):addClass( class ) return block:node( styles ):node( hat ):node( excerpt ):node( more ) end -- Entry points for backwards compatibility function p.lead( frame ) return p.main( frame ) end function p.excerpt( frame ) return p.main( frame ) end return p plux9uaavcv1i9pefeeu9hy76m3ls5z 797351 797350 2022-12-30T12:50:11Z en>Sophivorus 0 Add "displaytitle" param 797351 Scribunto text/plain -- Module:Excerpt implements the Excerpt template -- Documentation https://www.mediawiki.org/wiki/Module:Excerpt -- By User:Sophivorus, User:Certes & others -- Version 1.6 -- License CC BY-SA-3.0 local Transcluder = require( 'Module:Transcluder' ) local yesno = require( 'Module:Yesno' ) local ok, config = pcall( require, 'Module:Excerpt/config' ) if not ok then config = {} end local p = {} -- Helper function to get arguments local args function getArg( key, default ) local value = args[ key ] if value and mw.text.trim( value ) ~= '' then return value end return default end -- Helper function to handle errors function getError( message, value ) if type( message ) == 'string' then message = Transcluder.getError( message, value ) end if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then message:node( '[[Category:' .. config.categories.errors .. ']]' ) end return message end -- Helper function to get localized messages function getMessage( key ) local ok, TNT = pcall( require, 'Module:TNT' ) if not ok then return key end return TNT.format( 'I18n/Module:Excerpt.tab', key ) end function p.main( frame ) args = Transcluder.parseArgs( frame ) -- Make sure the requested page exists local page = getArg( 1 ) if not page then return getError( 'no-page' ) end local title = mw.title.new(page) if not title then return getError( 'no-page' ) end if title.isRedirect then title = title.redirectTarget end if not title.exists then return getError( 'page-not-found', page ) end page = title.prefixedText -- Set variables from the template parameters local section = getArg( 2, mw.ustring.match( getArg( 1 ), '[^#]+#(.+)' ) ) local hat = yesno( getArg( 'hat', true ) ) local edit = yesno( getArg( 'edit', true ) ) local this = getArg( 'this' ) local only = getArg( 'only' ) local files = getArg( 'files', getArg( 'file', ( only == 'file' and 1 ) ) ) local lists = getArg( 'lists', getArg( 'list', ( only == 'list' and 1 ) ) ) local tables = getArg( 'tables', getArg( 'table', ( only == 'table' and 1 ) ) ) local templates = getArg( 'templates', getArg( 'template', ( only == 'template' and 1 ) ) ) local paragraphs = getArg( 'paragraphs', getArg( 'paragraph', ( only == 'paragraph' and 1 ) ) ) local references = getArg( 'references' ) local subsections = not yesno( getArg( 'subsections' ) ) local noLinks = not yesno( getArg( 'links', true ) ) local noBold = not yesno( getArg( 'bold' ) ) local onlyFreeFiles = yesno( getArg( 'onlyfreefiles', true ) ) local inline = yesno( getArg( 'inline' ) ) local quote = yesno( getArg( 'quote' ) ) local more = yesno( getArg( 'more' ) ) local class = getArg( 'class' ) local displaytitle = getArg( 'displaytitle' ) or page -- Build the hatnote if hat and not inline then if this then hat = this elseif quote then hat = getMessage( 'this' ) elseif only then hat = getMessage( only ) else hat = getMessage( 'section' ) end hat = hat .. ' ' .. getMessage( 'excerpt' ) .. ' ' if section and not fragment then hat = hat .. '[[:' .. page .. '#' .. mw.uri.anchorEncode( section ) .. '|' .. displaytitle .. ' § ' .. mw.ustring.gsub( section, '%[%[([^]|]+)|?[^]]*%]%]', '%1' ) .. ']].' -- remove nested links else hat = hat .. '[[:' .. page .. '|' .. displaytitle .. ']].' end if edit then hat = hat .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. title:fullUrl( 'action=edit' ) .. ' ' .. mw.message.new( 'editsection' ):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' end if config.hat then hat = config.hat .. hat .. '}}' hat = frame:preprocess( hat ) else hat = mw.html.create( 'div' ):addClass( 'dablink excerpt-hat' ):wikitext( hat ) end else hat = nil end -- Build the "Read more" link if more and not inline then more = "'''[[" .. page .. '#' .. ( section or '' ) .. "|" .. getMessage( 'more' ) .. "]]'''" more = mw.html.create( 'div' ):addClass( 'noprint excerpt-more' ):wikitext( more ) else more = nil end -- Build the options for Module:Transcluder out of the template parameters and the desired defaults local options = { files = files, lists = lists, tables = tables, paragraphs = paragraphs, sections = subsections, categories = 0, references = references, only = only and mw.text.trim( only, 's' ) .. 's', noLinks = noLinks, noBold = noBold, noSelfLinks = true, noNonFreeFiles = onlyFreeFiles, noBehaviorSwitches = true, fixReferences = true, linkBold = true, } -- Get the excerpt itself local title = page .. '#' .. ( section or '' ) local ok, excerpt = pcall( Transcluder.get, title, options ) if not ok then return getError( excerpt ) end if mw.text.trim( excerpt ) == '' and not only then if section then return getError( 'section-empty', section ) else return getError( 'lead-empty' ) end end -- If no file was found, try to get one from the infobox local fileNamespaces = Transcluder.getNamespaces( 'File' ) if ( ( only == 'file' or only == 'files' ) or ( not only and ( files ~= '0' or not files ) ) ) and -- caller asked for files not Transcluder.matchAny( excerpt, '%[%[', fileNamespaces, ':' ) and -- and there are no files in Transcluder's output config.captions -- and we have the config option required to try finding files in templates then -- We cannot distinguish the infobox from the other templates so we search them all local infobox = Transcluder.getTemplates( excerpt ); infobox = table.concat( infobox ) local parameters = Transcluder.getParameters( infobox ) local file, captions, caption for _, pair in pairs( config.captions ) do file = pair[1] file = parameters[file] if file and Transcluder.matchAny( file, '^.*%.', { '[Jj][Pp][Ee]?[Gg]', '[Pp][Nn][Gg]', '[Gg][Ii][Ff]', '[Ss][Vv][Gg]' }, '.*' ) then file = mw.ustring.match( file, '%[?%[?.-:([^{|]+)%]?%]?' ) or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs( captions ) do if parameters[ p ] then caption = parameters[ p ] break end end excerpt = '[[File:' .. file .. '|thumb|' .. ( caption or '' ) .. ']]' .. excerpt if ( onlyFreeFiles ) then excerpt = Transcluder.removeNonFreeFiles( excerpt ) end break end end end -- Unlike other elements, templates are filtered here -- because we had to search the infoboxes for files local trash if only and ( only == 'template' or only == 'templates' ) then trash, excerpt = Transcluder.getTemplates( excerpt, templates ); else -- Remove blacklisted templates local blacklist = config.blacklist and table.concat( config.blacklist, ',' ) or '' if templates then if string.sub( templates, 1, 1 ) == '-' then --Unwanted templates. Append to blacklist blacklist = templates .. ',' .. blacklist else --Wanted templates. Replaces blacklist and acts as whitelist blacklist = templates end else blacklist = '-' .. blacklist end trash, excerpt = Transcluder.getTemplates( excerpt, blacklist ); end -- Remove extra line breaks but leave one before and after so the parser interprets lists, tables, etc. correctly excerpt = mw.text.trim( excerpt ) excerpt = string.gsub( excerpt, '\n\n\n+', '\n\n' ) excerpt = '\n' .. excerpt .. '\n' -- Remove nested categories excerpt = frame:preprocess( excerpt ) local categories, excerpt = Transcluder.getCategories( excerpt, options.categories ) -- Add tracking categories if config.categories then local contentCategory = config.categories.content if contentCategory and mw.title.getCurrentTitle().isContentPage then excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ mw.title.getCurrentTitle().namespace ] if namespaceCategory then excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end end -- Load the styles local styles if config.styles then styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) end -- Combine and return the elements if inline then return mw.text.trim( excerpt ) end local tag = 'div' if quote then tag = 'blockquote' end excerpt = mw.html.create( 'div' ):addClass( 'excerpt' ):wikitext( excerpt ) local block = mw.html.create( tag ):addClass( 'excerpt-block' ):addClass( class ) return block:node( styles ):node( hat ):node( excerpt ):node( more ) end -- Entry points for backwards compatibility function p.lead( frame ) return p.main( frame ) end function p.excerpt( frame ) return p.main( frame ) end return p i4wn78565teiz7ppsa9mmdlbp9c2tqw 797352 797351 2023-03-16T12:50:52Z en>Sophivorus 0 Set enwiki as central version 797352 Scribunto text/plain -- Module:Excerpt implements the Excerpt template -- Documentation https://en.wikipedia.org/wiki/Module:Excerpt -- By User:Sophivorus, User:Certes & others -- Version 1.6 -- License CC BY-SA-3.0 local Transcluder = require( 'Module:Transcluder' ) local yesno = require( 'Module:Yesno' ) local ok, config = pcall( require, 'Module:Excerpt/config' ) if not ok then config = {} end local p = {} -- Helper function to get arguments local args function getArg( key, default ) local value = args[ key ] if value and mw.text.trim( value ) ~= '' then return value end return default end -- Helper function to handle errors function getError( message, value ) if type( message ) == 'string' then message = Transcluder.getError( message, value ) end if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then message:node( '[[Category:' .. config.categories.errors .. ']]' ) end return message end -- Helper function to get localized messages function getMessage( key ) local ok, TNT = pcall( require, 'Module:TNT' ) if not ok then return key end return TNT.format( 'I18n/Module:Excerpt.tab', key ) end function p.main( frame ) args = Transcluder.parseArgs( frame ) -- Make sure the requested page exists local page = getArg( 1 ) if not page then return getError( 'no-page' ) end local title = mw.title.new(page) if not title then return getError( 'no-page' ) end if title.isRedirect then title = title.redirectTarget end if not title.exists then return getError( 'page-not-found', page ) end page = title.prefixedText -- Set variables from the template parameters local section = getArg( 2, mw.ustring.match( getArg( 1 ), '[^#]+#(.+)' ) ) local hat = yesno( getArg( 'hat', true ) ) local edit = yesno( getArg( 'edit', true ) ) local this = getArg( 'this' ) local only = getArg( 'only' ) local files = getArg( 'files', getArg( 'file', ( only == 'file' and 1 ) ) ) local lists = getArg( 'lists', getArg( 'list', ( only == 'list' and 1 ) ) ) local tables = getArg( 'tables', getArg( 'table', ( only == 'table' and 1 ) ) ) local templates = getArg( 'templates', getArg( 'template', ( only == 'template' and 1 ) ) ) local paragraphs = getArg( 'paragraphs', getArg( 'paragraph', ( only == 'paragraph' and 1 ) ) ) local references = getArg( 'references' ) local subsections = not yesno( getArg( 'subsections' ) ) local noLinks = not yesno( getArg( 'links', true ) ) local noBold = not yesno( getArg( 'bold' ) ) local onlyFreeFiles = yesno( getArg( 'onlyfreefiles', true ) ) local inline = yesno( getArg( 'inline' ) ) local quote = yesno( getArg( 'quote' ) ) local more = yesno( getArg( 'more' ) ) local class = getArg( 'class' ) local displaytitle = getArg( 'displaytitle' ) or page -- Build the hatnote if hat and not inline then if this then hat = this elseif quote then hat = getMessage( 'this' ) elseif only then hat = getMessage( only ) else hat = getMessage( 'section' ) end hat = hat .. ' ' .. getMessage( 'excerpt' ) .. ' ' if section and not fragment then hat = hat .. '[[:' .. page .. '#' .. mw.uri.anchorEncode( section ) .. '|' .. displaytitle .. ' § ' .. mw.ustring.gsub( section, '%[%[([^]|]+)|?[^]]*%]%]', '%1' ) .. ']].' -- remove nested links else hat = hat .. '[[:' .. page .. '|' .. displaytitle .. ']].' end if edit then hat = hat .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. title:fullUrl( 'action=edit' ) .. ' ' .. mw.message.new( 'editsection' ):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' end if config.hat then hat = config.hat .. hat .. '}}' hat = frame:preprocess( hat ) else hat = mw.html.create( 'div' ):addClass( 'dablink excerpt-hat' ):wikitext( hat ) end else hat = nil end -- Build the "Read more" link if more and not inline then more = "'''[[" .. page .. '#' .. ( section or '' ) .. "|" .. getMessage( 'more' ) .. "]]'''" more = mw.html.create( 'div' ):addClass( 'noprint excerpt-more' ):wikitext( more ) else more = nil end -- Build the options for Module:Transcluder out of the template parameters and the desired defaults local options = { files = files, lists = lists, tables = tables, paragraphs = paragraphs, sections = subsections, categories = 0, references = references, only = only and mw.text.trim( only, 's' ) .. 's', noLinks = noLinks, noBold = noBold, noSelfLinks = true, noNonFreeFiles = onlyFreeFiles, noBehaviorSwitches = true, fixReferences = true, linkBold = true, } -- Get the excerpt itself local title = page .. '#' .. ( section or '' ) local ok, excerpt = pcall( Transcluder.get, title, options ) if not ok then return getError( excerpt ) end if mw.text.trim( excerpt ) == '' and not only then if section then return getError( 'section-empty', section ) else return getError( 'lead-empty' ) end end -- If no file was found, try to get one from the infobox local fileNamespaces = Transcluder.getNamespaces( 'File' ) if ( ( only == 'file' or only == 'files' ) or ( not only and ( files ~= '0' or not files ) ) ) and -- caller asked for files not Transcluder.matchAny( excerpt, '%[%[', fileNamespaces, ':' ) and -- and there are no files in Transcluder's output config.captions -- and we have the config option required to try finding files in templates then -- We cannot distinguish the infobox from the other templates so we search them all local infobox = Transcluder.getTemplates( excerpt ); infobox = table.concat( infobox ) local parameters = Transcluder.getParameters( infobox ) local file, captions, caption for _, pair in pairs( config.captions ) do file = pair[1] file = parameters[file] if file and Transcluder.matchAny( file, '^.*%.', { '[Jj][Pp][Ee]?[Gg]', '[Pp][Nn][Gg]', '[Gg][Ii][Ff]', '[Ss][Vv][Gg]' }, '.*' ) then file = mw.ustring.match( file, '%[?%[?.-:([^{|]+)%]?%]?' ) or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs( captions ) do if parameters[ p ] then caption = parameters[ p ] break end end excerpt = '[[File:' .. file .. '|thumb|' .. ( caption or '' ) .. ']]' .. excerpt if ( onlyFreeFiles ) then excerpt = Transcluder.removeNonFreeFiles( excerpt ) end break end end end -- Unlike other elements, templates are filtered here -- because we had to search the infoboxes for files local trash if only and ( only == 'template' or only == 'templates' ) then trash, excerpt = Transcluder.getTemplates( excerpt, templates ); else -- Remove blacklisted templates local blacklist = config.blacklist and table.concat( config.blacklist, ',' ) or '' if templates then if string.sub( templates, 1, 1 ) == '-' then --Unwanted templates. Append to blacklist blacklist = templates .. ',' .. blacklist else --Wanted templates. Replaces blacklist and acts as whitelist blacklist = templates end else blacklist = '-' .. blacklist end trash, excerpt = Transcluder.getTemplates( excerpt, blacklist ); end -- Remove extra line breaks but leave one before and after so the parser interprets lists, tables, etc. correctly excerpt = mw.text.trim( excerpt ) excerpt = string.gsub( excerpt, '\n\n\n+', '\n\n' ) excerpt = '\n' .. excerpt .. '\n' -- Remove nested categories excerpt = frame:preprocess( excerpt ) local categories, excerpt = Transcluder.getCategories( excerpt, options.categories ) -- Add tracking categories if config.categories then local contentCategory = config.categories.content if contentCategory and mw.title.getCurrentTitle().isContentPage then excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ mw.title.getCurrentTitle().namespace ] if namespaceCategory then excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end end -- Load the styles local styles if config.styles then styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) end -- Combine and return the elements if inline then return mw.text.trim( excerpt ) end local tag = 'div' if quote then tag = 'blockquote' end excerpt = mw.html.create( 'div' ):addClass( 'excerpt' ):wikitext( excerpt ) local block = mw.html.create( tag ):addClass( 'excerpt-block' ):addClass( class ) return block:node( styles ):node( hat ):node( excerpt ):node( more ) end -- Entry points for backwards compatibility function p.lead( frame ) return p.main( frame ) end function p.excerpt( frame ) return p.main( frame ) end return p l0zcpmae9tsl49bgux28wy3ut7hxt11 797353 797352 2023-03-22T21:41:18Z en>Sophivorus 0 Add briefdates feature 797353 Scribunto text/plain -- Module:Excerpt implements the Excerpt template -- Documentation and master version: https://en.wikipedia.org/wiki/Module:Excerpt -- Authors: User:Sophivorus, User:Certes & others -- License: CC-BY-SA-3.0 local Transcluder = require( 'Module:Transcluder' ) local yesno = require( 'Module:Yesno' ) local ok, config = pcall( require, 'Module:Excerpt/config' ) if not ok then config = {} end local p = {} -- Helper function to get arguments local args function getArg( key, default ) local value = args[ key ] if value and mw.text.trim( value ) ~= '' then return value end return default end -- Helper function to handle errors function getError( message, value ) if type( message ) == 'string' then message = Transcluder.getError( message, value ) end if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then message:node( '[[Category:' .. config.categories.errors .. ']]' ) end return message end -- Helper function to get localized messages function getMessage( key ) local ok, TNT = pcall( require, 'Module:TNT' ) if not ok then return key end return TNT.format( 'I18n/Module:Excerpt.tab', key ) end function p.main( frame ) args = Transcluder.parseArgs( frame ) -- Make sure the requested page exists local page = getArg( 1 ) if not page then return getError( 'no-page' ) end local title = mw.title.new(page) if not title then return getError( 'no-page' ) end if title.isRedirect then title = title.redirectTarget end if not title.exists then return getError( 'page-not-found', page ) end page = title.prefixedText -- Set variables from the template parameters local section = getArg( 2, mw.ustring.match( getArg( 1 ), '[^#]+#(.+)' ) ) local hat = yesno( getArg( 'hat', true ) ) local edit = yesno( getArg( 'edit', true ) ) local this = getArg( 'this' ) local only = getArg( 'only' ) local files = getArg( 'files', getArg( 'file', ( only == 'file' and 1 ) ) ) local lists = getArg( 'lists', getArg( 'list', ( only == 'list' and 1 ) ) ) local tables = getArg( 'tables', getArg( 'table', ( only == 'table' and 1 ) ) ) local templates = getArg( 'templates', getArg( 'template', ( only == 'template' and 1 ) ) ) local paragraphs = getArg( 'paragraphs', getArg( 'paragraph', ( only == 'paragraph' and 1 ) ) ) local references = getArg( 'references' ) local subsections = not yesno( getArg( 'subsections' ) ) local noLinks = not yesno( getArg( 'links', true ) ) local noBold = not yesno( getArg( 'bold' ) ) local onlyFreeFiles = yesno( getArg( 'onlyfreefiles', true ) ) local briefDates = yesno( getArg( 'briefdates', false ) ) local inline = yesno( getArg( 'inline' ) ) local quote = yesno( getArg( 'quote' ) ) local more = yesno( getArg( 'more' ) ) local class = getArg( 'class' ) local displaytitle = getArg( 'displaytitle' ) or page -- Build the hatnote if hat and not inline then if this then hat = this elseif quote then hat = getMessage( 'this' ) elseif only then hat = getMessage( only ) else hat = getMessage( 'section' ) end hat = hat .. ' ' .. getMessage( 'excerpt' ) .. ' ' if section and not fragment then hat = hat .. '[[:' .. page .. '#' .. mw.uri.anchorEncode( section ) .. '|' .. displaytitle .. ' § ' .. mw.ustring.gsub( section, '%[%[([^]|]+)|?[^]]*%]%]', '%1' ) .. ']].' -- remove nested links else hat = hat .. '[[:' .. page .. '|' .. displaytitle .. ']].' end if edit then hat = hat .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. title:fullUrl( 'action=edit' ) .. ' ' .. mw.message.new( 'editsection' ):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' end if config.hat then hat = config.hat .. hat .. '}}' hat = frame:preprocess( hat ) else hat = mw.html.create( 'div' ):addClass( 'dablink excerpt-hat' ):wikitext( hat ) end else hat = nil end -- Build the "Read more" link if more and not inline then more = "'''[[" .. page .. '#' .. ( section or '' ) .. "|" .. getMessage( 'more' ) .. "]]'''" more = mw.html.create( 'div' ):addClass( 'noprint excerpt-more' ):wikitext( more ) else more = nil end -- Build the options for Module:Transcluder out of the template parameters and the desired defaults local options = { files = files, lists = lists, tables = tables, paragraphs = paragraphs, sections = subsections, categories = 0, references = references, only = only and mw.text.trim( only, 's' ) .. 's', noLinks = noLinks, noBold = noBold, noSelfLinks = true, noNonFreeFiles = onlyFreeFiles, noBehaviorSwitches = true, fixReferences = true, linkBold = true, } -- Get the excerpt itself local title = page .. '#' .. ( section or '' ) local ok, excerpt = pcall( Transcluder.get, title, options ) if not ok then return getError( excerpt ) end if mw.text.trim( excerpt ) == '' and not only then if section then return getError( 'section-empty', section ) else return getError( 'lead-empty' ) end end -- Fix birth and death dates, but only in the first paragraph if briefDates then local startpos = 1 -- skip initial templates local s local e = 0 repeat startpos = e + 1 s, e = mw.ustring.find( excerpt, "%s*%b{}%s*", startpos ) until not s or s > startpos s, e = mw.ustring.find( excerpt, "%b()", startpos ) -- get (...), which may be (year–year) if s and s < startpos + 100 then -- look only near the start local year1, conjunction, year2 = mw.ustring.match( mw.ustring.sub( excerpt, s, e ), '(%d%d%d+)(.-)(%d%d%d+)' ) if year1 and year2 and (mw.ustring.match( conjunction, '[%-–—]' ) or mw.ustring.match( conjunction, '{{%s*[sS]nd%s*}}' )) then local y1 = tonumber(year1) local y2 = tonumber(year2) if y2 > y1 and y2 < y1 + 125 and y1 <= tonumber( os.date( "%Y" )) then excerpt = mw.ustring.sub( excerpt, 1, s ) .. year1 .. "–" .. year2 .. mw.ustring.sub( excerpt, e ) end end end end -- If no file was found, try to get one from the infobox local fileNamespaces = Transcluder.getNamespaces( 'File' ) if ( ( only == 'file' or only == 'files' ) or ( not only and ( files ~= '0' or not files ) ) ) and -- caller asked for files not Transcluder.matchAny( excerpt, '%[%[', fileNamespaces, ':' ) and -- and there are no files in Transcluder's output config.captions -- and we have the config option required to try finding files in templates then -- We cannot distinguish the infobox from the other templates so we search them all local infobox = Transcluder.getTemplates( excerpt ); infobox = table.concat( infobox ) local parameters = Transcluder.getParameters( infobox ) local file, captions, caption for _, pair in pairs( config.captions ) do file = pair[1] file = parameters[file] if file and Transcluder.matchAny( file, '^.*%.', { '[Jj][Pp][Ee]?[Gg]', '[Pp][Nn][Gg]', '[Gg][Ii][Ff]', '[Ss][Vv][Gg]' }, '.*' ) then file = mw.ustring.match( file, '%[?%[?.-:([^{|]+)%]?%]?' ) or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs( captions ) do if parameters[ p ] then caption = parameters[ p ] break end end excerpt = '[[File:' .. file .. '|thumb|' .. ( caption or '' ) .. ']]' .. excerpt if ( onlyFreeFiles ) then excerpt = Transcluder.removeNonFreeFiles( excerpt ) end break end end end -- Unlike other elements, templates are filtered here -- because we had to search the infoboxes for files local trash if only and ( only == 'template' or only == 'templates' ) then trash, excerpt = Transcluder.getTemplates( excerpt, templates ); else -- Remove blacklisted templates local blacklist = config.blacklist and table.concat( config.blacklist, ',' ) or '' if templates then if string.sub( templates, 1, 1 ) == '-' then --Unwanted templates. Append to blacklist blacklist = templates .. ',' .. blacklist else --Wanted templates. Replaces blacklist and acts as whitelist blacklist = templates end else blacklist = '-' .. blacklist end trash, excerpt = Transcluder.getTemplates( excerpt, blacklist ); end -- Remove extra line breaks but leave one before and after so the parser interprets lists, tables, etc. correctly excerpt = mw.text.trim( excerpt ) excerpt = string.gsub( excerpt, '\n\n\n+', '\n\n' ) excerpt = '\n' .. excerpt .. '\n' -- Remove nested categories excerpt = frame:preprocess( excerpt ) local categories, excerpt = Transcluder.getCategories( excerpt, options.categories ) -- Add tracking categories if config.categories then local contentCategory = config.categories.content if contentCategory and mw.title.getCurrentTitle().isContentPage then excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ mw.title.getCurrentTitle().namespace ] if namespaceCategory then excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end end -- Load the styles local styles if config.styles then styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) end -- Combine and return the elements if inline then return mw.text.trim( excerpt ) end local tag = 'div' if quote then tag = 'blockquote' end excerpt = mw.html.create( 'div' ):addClass( 'excerpt' ):wikitext( excerpt ) local block = mw.html.create( tag ):addClass( 'excerpt-block' ):addClass( class ) return block:node( styles ):node( hat ):node( excerpt ):node( more ) end -- Entry points for backwards compatibility function p.lead( frame ) return p.main( frame ) end function p.excerpt( frame ) return p.main( frame ) end return p 25rhq28iilv3arevy4fyzt5ybuz36mh 797354 797353 2023-07-10T16:17:53Z en>Sophivorus 0 Update to latest: refine error handling for invalid titles, make some methods local, add Aidan9382 as author, remove old reference to fragment param 797354 Scribunto text/plain -- Module:Excerpt implements the Excerpt template -- Documentation and master version: https://en.wikipedia.org/wiki/Module:Excerpt -- Authors: User:Sophivorus, User:Certes, User:Aidan9382 & others -- License: CC-BY-SA-3.0 local Transcluder = require( 'Module:Transcluder' ) local yesno = require( 'Module:Yesno' ) local ok, config = pcall( require, 'Module:Excerpt/config' ) if not ok then config = {} end local p = {} -- Helper function to get arguments local args local function getArg( key, default ) local value = args[ key ] if value and mw.text.trim( value ) ~= '' then return value end return default end -- Helper function to handle errors local function getError( message, value ) if type( message ) == 'string' then message = Transcluder.getError( message, value ) end if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then message:node( '[[Category:' .. config.categories.errors .. ']]' ) end return message end -- Helper function to get localized messages local function getMessage( key ) local ok, TNT = pcall( require, 'Module:TNT' ) if not ok then return key end return TNT.format( 'I18n/Module:Excerpt.tab', key ) end -- Main entry point for templates function p.main( frame ) args = Transcluder.parseArgs( frame ) -- Make sure the requested page exists local page = getArg( 1 ) if not page or page == '{{{1}}}' then return getError( 'no-page' ) end local title = mw.title.new(page) if not title then return getError( 'invalid-title', page ) end if title.isRedirect then title = title.redirectTarget end if not title.exists then return getError( 'page-not-found', page ) end page = title.prefixedText -- Set variables from the template parameters local section = getArg( 2, mw.ustring.match( getArg( 1 ), '[^#]+#(.+)' ) ) local hat = yesno( getArg( 'hat', true ) ) local edit = yesno( getArg( 'edit', true ) ) local this = getArg( 'this' ) local only = getArg( 'only' ) local files = getArg( 'files', getArg( 'file', ( only == 'file' and 1 ) ) ) local lists = getArg( 'lists', getArg( 'list', ( only == 'list' and 1 ) ) ) local tables = getArg( 'tables', getArg( 'table', ( only == 'table' and 1 ) ) ) local templates = getArg( 'templates', getArg( 'template', ( only == 'template' and 1 ) ) ) local paragraphs = getArg( 'paragraphs', getArg( 'paragraph', ( only == 'paragraph' and 1 ) ) ) local references = getArg( 'references' ) local subsections = not yesno( getArg( 'subsections' ) ) local noLinks = not yesno( getArg( 'links', true ) ) local noBold = not yesno( getArg( 'bold' ) ) local onlyFreeFiles = yesno( getArg( 'onlyfreefiles', true ) ) local briefDates = yesno( getArg( 'briefdates', false ) ) local inline = yesno( getArg( 'inline' ) ) local quote = yesno( getArg( 'quote' ) ) local more = yesno( getArg( 'more' ) ) local class = getArg( 'class' ) local displaytitle = getArg( 'displaytitle' ) or page -- Build the hatnote if hat and not inline then if this then hat = this elseif quote then hat = getMessage( 'this' ) elseif only then hat = getMessage( only ) else hat = getMessage( 'section' ) end hat = hat .. ' ' .. getMessage( 'excerpt' ) .. ' ' if section then hat = hat .. '[[:' .. page .. '#' .. mw.uri.anchorEncode( section ) .. '|' .. displaytitle .. ' § ' .. mw.ustring.gsub( section, '%[%[([^]|]+)|?[^]]*%]%]', '%1' ) .. ']].' -- remove nested links else hat = hat .. '[[:' .. page .. '|' .. displaytitle .. ']].' end if edit then hat = hat .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. title:fullUrl( 'action=edit' ) .. ' ' .. mw.message.new( 'editsection' ):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' end if config.hat then hat = config.hat .. hat .. '}}' hat = frame:preprocess( hat ) else hat = mw.html.create( 'div' ):addClass( 'dablink excerpt-hat' ):wikitext( hat ) end else hat = nil end -- Build the "Read more" link if more and not inline then more = "'''[[" .. page .. '#' .. ( section or '' ) .. "|" .. getMessage( 'more' ) .. "]]'''" more = mw.html.create( 'div' ):addClass( 'noprint excerpt-more' ):wikitext( more ) else more = nil end -- Build the options for Module:Transcluder out of the template parameters and the desired defaults local options = { files = files, lists = lists, tables = tables, paragraphs = paragraphs, sections = subsections, categories = 0, references = references, only = only and mw.text.trim( only, 's' ) .. 's', noLinks = noLinks, noBold = noBold, noSelfLinks = true, noNonFreeFiles = onlyFreeFiles, noBehaviorSwitches = true, fixReferences = true, linkBold = true, } -- Get the excerpt itself local title = page .. '#' .. ( section or '' ) local ok, excerpt = pcall( Transcluder.get, title, options ) if not ok then return getError( excerpt ) end if mw.text.trim( excerpt ) == '' and not only then if section then return getError( 'section-empty', section ) else return getError( 'lead-empty' ) end end -- Fix birth and death dates, but only in the first paragraph if briefDates then local startpos = 1 -- skip initial templates local s local e = 0 repeat startpos = e + 1 s, e = mw.ustring.find( excerpt, "%s*%b{}%s*", startpos ) until not s or s > startpos s, e = mw.ustring.find( excerpt, "%b()", startpos ) -- get (...), which may be (year–year) if s and s < startpos + 100 then -- look only near the start local year1, conjunction, year2 = mw.ustring.match( mw.ustring.sub( excerpt, s, e ), '(%d%d%d+)(.-)(%d%d%d+)' ) if year1 and year2 and (mw.ustring.match( conjunction, '[%-–—]' ) or mw.ustring.match( conjunction, '{{%s*[sS]nd%s*}}' )) then local y1 = tonumber(year1) local y2 = tonumber(year2) if y2 > y1 and y2 < y1 + 125 and y1 <= tonumber( os.date( "%Y" )) then excerpt = mw.ustring.sub( excerpt, 1, s ) .. year1 .. "–" .. year2 .. mw.ustring.sub( excerpt, e ) end end end end -- If no file was found, try to get one from the infobox local fileNamespaces = Transcluder.getNamespaces( 'File' ) if ( ( only == 'file' or only == 'files' ) or ( not only and ( files ~= '0' or not files ) ) ) and -- caller asked for files not Transcluder.matchAny( excerpt, '%[%[', fileNamespaces, ':' ) and -- and there are no files in Transcluder's output config.captions -- and we have the config option required to try finding files in templates then -- We cannot distinguish the infobox from the other templates so we search them all local infobox = Transcluder.getTemplates( excerpt ); infobox = table.concat( infobox ) local parameters = Transcluder.getParameters( infobox ) local file, captions, caption for _, pair in pairs( config.captions ) do file = pair[1] file = parameters[file] if file and Transcluder.matchAny( file, '^.*%.', { '[Jj][Pp][Ee]?[Gg]', '[Pp][Nn][Gg]', '[Gg][Ii][Ff]', '[Ss][Vv][Gg]' }, '.*' ) then file = mw.ustring.match( file, '%[?%[?.-:([^{|]+)%]?%]?' ) or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs( captions ) do if parameters[ p ] then caption = parameters[ p ] break end end excerpt = '[[File:' .. file .. '|thumb|' .. ( caption or '' ) .. ']]' .. excerpt if ( onlyFreeFiles ) then excerpt = Transcluder.removeNonFreeFiles( excerpt ) end break end end end -- Unlike other elements, templates are filtered here -- because we had to search the infoboxes for files local trash if only and ( only == 'template' or only == 'templates' ) then trash, excerpt = Transcluder.getTemplates( excerpt, templates ); else -- Remove blacklisted templates local blacklist = config.blacklist and table.concat( config.blacklist, ',' ) or '' if templates then if string.sub( templates, 1, 1 ) == '-' then --Unwanted templates. Append to blacklist blacklist = templates .. ',' .. blacklist else --Wanted templates. Replaces blacklist and acts as whitelist blacklist = templates end else blacklist = '-' .. blacklist end trash, excerpt = Transcluder.getTemplates( excerpt, blacklist ); end -- Remove extra line breaks but leave one before and after so the parser interprets lists, tables, etc. correctly excerpt = mw.text.trim( excerpt ) excerpt = string.gsub( excerpt, '\n\n\n+', '\n\n' ) excerpt = '\n' .. excerpt .. '\n' -- Remove nested categories excerpt = frame:preprocess( excerpt ) local categories, excerpt = Transcluder.getCategories( excerpt, options.categories ) -- Add tracking categories if config.categories then local contentCategory = config.categories.content if contentCategory and mw.title.getCurrentTitle().isContentPage then excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ mw.title.getCurrentTitle().namespace ] if namespaceCategory then excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end end -- Load the styles local styles if config.styles then styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) end -- Combine and return the elements if inline then return mw.text.trim( excerpt ) end local tag = 'div' if quote then tag = 'blockquote' end excerpt = mw.html.create( 'div' ):addClass( 'excerpt' ):wikitext( excerpt ) local block = mw.html.create( tag ):addClass( 'excerpt-block' ):addClass( class ) return block:node( styles ):node( hat ):node( excerpt ):node( more ) end -- Entry points for backwards compatibility function p.lead( frame ) return p.main( frame ) end function p.excerpt( frame ) return p.main( frame ) end return p otwcbfmbi3hwmrd1unz7dux28iop4li 797355 797354 2025-01-28T01:57:40Z en>Arthurfragoso 0 Use infobox CSS class if it exists (mainly for dark mode compatibility) 797355 Scribunto text/plain -- Module:Excerpt implements the Excerpt template -- Documentation and master version: https://en.wikipedia.org/wiki/Module:Excerpt -- Authors: User:Sophivorus, User:Certes, User:Aidan9382 & others -- License: CC-BY-SA-3.0 local Transcluder = require( 'Module:Transcluder' ) local yesno = require( 'Module:Yesno' ) local ok, config = pcall( require, 'Module:Excerpt/config' ) if not ok then config = {} end local p = {} -- Helper function to get arguments local args local function getArg( key, default ) local value = args[ key ] if value and mw.text.trim( value ) ~= '' then return value end return default end -- Helper function to handle errors local function getError( message, value ) if type( message ) == 'string' then message = Transcluder.getError( message, value ) end if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then message:node( '[[Category:' .. config.categories.errors .. ']]' ) end return message end -- Helper function to get localized messages local function getMessage( key ) local ok, TNT = pcall( require, 'Module:TNT' ) if not ok then return key end return TNT.format( 'I18n/Module:Excerpt.tab', key ) end -- Main entry point for templates function p.main( frame ) args = Transcluder.parseArgs( frame ) -- Make sure the requested page exists local page = getArg( 1 ) if not page or page == '{{{1}}}' then return getError( 'no-page' ) end local title = mw.title.new(page) if not title then return getError( 'invalid-title', page ) end if title.isRedirect then title = title.redirectTarget end if not title.exists then return getError( 'page-not-found', page ) end page = title.prefixedText -- Set variables from the template parameters local section = getArg( 2, mw.ustring.match( getArg( 1 ), '[^#]+#(.+)' ) ) local hat = yesno( getArg( 'hat', true ) ) local edit = yesno( getArg( 'edit', true ) ) local this = getArg( 'this' ) local only = getArg( 'only' ) local files = getArg( 'files', getArg( 'file', ( only == 'file' and 1 ) ) ) local lists = getArg( 'lists', getArg( 'list', ( only == 'list' and 1 ) ) ) local tables = getArg( 'tables', getArg( 'table', ( only == 'table' and 1 ) ) ) local templates = getArg( 'templates', getArg( 'template', ( only == 'template' and 1 ) ) ) local paragraphs = getArg( 'paragraphs', getArg( 'paragraph', ( only == 'paragraph' and 1 ) ) ) local references = getArg( 'references' ) local subsections = not yesno( getArg( 'subsections' ) ) local noLinks = not yesno( getArg( 'links', true ) ) local noBold = not yesno( getArg( 'bold' ) ) local onlyFreeFiles = yesno( getArg( 'onlyfreefiles', true ) ) local briefDates = yesno( getArg( 'briefdates', false ) ) local inline = yesno( getArg( 'inline' ) ) local quote = yesno( getArg( 'quote' ) ) local more = yesno( getArg( 'more' ) ) local class = getArg( 'class' ) local displaytitle = getArg( 'displaytitle' ) or page -- Build the hatnote if hat and not inline then if this then hat = this elseif quote then hat = getMessage( 'this' ) elseif only then hat = getMessage( only ) else hat = getMessage( 'section' ) end hat = hat .. ' ' .. getMessage( 'excerpt' ) .. ' ' if section then hat = hat .. '[[:' .. page .. '#' .. mw.uri.anchorEncode( section ) .. '|' .. displaytitle .. ' § ' .. mw.ustring.gsub( section, '%[%[([^]|]+)|?[^]]*%]%]', '%1' ) .. ']].' -- remove nested links else hat = hat .. '[[:' .. page .. '|' .. displaytitle .. ']].' end if edit then hat = hat .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. title:fullUrl( 'action=edit' ) .. ' ' .. mw.message.new( 'editsection' ):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' end if config.hat then hat = config.hat .. hat .. '}}' hat = frame:preprocess( hat ) else hat = mw.html.create( 'div' ):addClass( 'dablink excerpt-hat' ):wikitext( hat ) end else hat = nil end -- Build the "Read more" link if more and not inline then more = "'''[[" .. page .. '#' .. ( section or '' ) .. "|" .. getMessage( 'more' ) .. "]]'''" more = mw.html.create( 'div' ):addClass( 'noprint excerpt-more' ):wikitext( more ) else more = nil end -- Build the options for Module:Transcluder out of the template parameters and the desired defaults local options = { files = files, lists = lists, tables = tables, paragraphs = paragraphs, sections = subsections, categories = 0, references = references, only = only and mw.text.trim( only, 's' ) .. 's', noLinks = noLinks, noBold = noBold, noSelfLinks = true, noNonFreeFiles = onlyFreeFiles, noBehaviorSwitches = true, fixReferences = true, linkBold = true, } -- Get the excerpt itself local title = page .. '#' .. ( section or '' ) local ok, excerpt = pcall( Transcluder.get, title, options ) if not ok then return getError( excerpt ) end if mw.text.trim( excerpt ) == '' and not only then if section then return getError( 'section-empty', section ) else return getError( 'lead-empty' ) end end -- Fix birth and death dates, but only in the first paragraph if briefDates then local startpos = 1 -- skip initial templates local s local e = 0 repeat startpos = e + 1 s, e = mw.ustring.find( excerpt, "%s*%b{}%s*", startpos ) until not s or s > startpos s, e = mw.ustring.find( excerpt, "%b()", startpos ) -- get (...), which may be (year–year) if s and s < startpos + 100 then -- look only near the start local year1, conjunction, year2 = mw.ustring.match( mw.ustring.sub( excerpt, s, e ), '(%d%d%d+)(.-)(%d%d%d+)' ) if year1 and year2 and (mw.ustring.match( conjunction, '[%-–—]' ) or mw.ustring.match( conjunction, '{{%s*[sS]nd%s*}}' )) then local y1 = tonumber(year1) local y2 = tonumber(year2) if y2 > y1 and y2 < y1 + 125 and y1 <= tonumber( os.date( "%Y" )) then excerpt = mw.ustring.sub( excerpt, 1, s ) .. year1 .. "–" .. year2 .. mw.ustring.sub( excerpt, e ) end end end end -- If no file was found, try to get one from the infobox local fileNamespaces = Transcluder.getNamespaces( 'File' ) if ( ( only == 'file' or only == 'files' ) or ( not only and ( files ~= '0' or not files ) ) ) and -- caller asked for files not Transcluder.matchAny( excerpt, '%[%[', fileNamespaces, ':' ) and -- and there are no files in Transcluder's output config.captions -- and we have the config option required to try finding files in templates then -- We cannot distinguish the infobox from the other templates so we search them all local infobox = Transcluder.getTemplates( excerpt ); infobox = table.concat( infobox ) local parameters = Transcluder.getParameters( infobox ) local file, captions, caption, cssclasses, cssclass for _, pair in pairs( config.captions ) do file = pair[1] file = parameters[file] if file and Transcluder.matchAny( file, '^.*%.', { '[Jj][Pp][Ee]?[Gg]', '[Pp][Nn][Gg]', '[Gg][Ii][Ff]', '[Ss][Vv][Gg]' }, '.*' ) then file = mw.ustring.match( file, '%[?%[?.-:([^{|]+)%]?%]?' ) or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs( captions ) do if parameters[ p ] then caption = parameters[ p ] break end end -- Check for CSS classes -- We opt to use skin-invert-image instead of skin-invert -- in all other cases, the CSS provided in the infobox is used if pair[3] then cssclasses = pair[3] for _, p in pairs(cssclasses) do if parameters[p] then cssclass = ((parameters[p] == 'skin-invert') and 'skin-invert-image' or parameters[p]) break end end end excerpt = '[[File:' .. file .. (cssclass and ('|class=' .. cssclass) or '') .. '|thumb|' .. (caption or '') .. ']]' .. excerpt if ( onlyFreeFiles ) then excerpt = Transcluder.removeNonFreeFiles( excerpt ) end break end end end -- Unlike other elements, templates are filtered here -- because we had to search the infoboxes for files local trash if only and ( only == 'template' or only == 'templates' ) then trash, excerpt = Transcluder.getTemplates( excerpt, templates ); else -- Remove blacklisted templates local blacklist = config.blacklist and table.concat( config.blacklist, ',' ) or '' if templates then if string.sub( templates, 1, 1 ) == '-' then --Unwanted templates. Append to blacklist blacklist = templates .. ',' .. blacklist else --Wanted templates. Replaces blacklist and acts as whitelist blacklist = templates end else blacklist = '-' .. blacklist end trash, excerpt = Transcluder.getTemplates( excerpt, blacklist ); end -- Remove extra line breaks but leave one before and after so the parser interprets lists, tables, etc. correctly excerpt = mw.text.trim( excerpt ) excerpt = string.gsub( excerpt, '\n\n\n+', '\n\n' ) excerpt = '\n' .. excerpt .. '\n' -- Remove nested categories excerpt = frame:preprocess( excerpt ) local categories, excerpt = Transcluder.getCategories( excerpt, options.categories ) -- Add tracking categories if config.categories then local contentCategory = config.categories.content if contentCategory and mw.title.getCurrentTitle().isContentPage then excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ mw.title.getCurrentTitle().namespace ] if namespaceCategory then excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end end -- Load the styles local styles if config.styles then styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) end -- Combine and return the elements if inline then return mw.text.trim( excerpt ) end local tag = 'div' if quote then tag = 'blockquote' end excerpt = mw.html.create( 'div' ):addClass( 'excerpt' ):wikitext( excerpt ) local block = mw.html.create( tag ):addClass( 'excerpt-block' ):addClass( class ) return block:node( styles ):node( hat ):node( excerpt ):node( more ) end -- Entry points for backwards compatibility function p.lead( frame ) return p.main( frame ) end function p.excerpt( frame ) return p.main( frame ) end return p 3iae3gnikt17y7anu02zdv3ie3mcivc 797356 797355 2025-10-02T13:24:14Z en>Sophivorus 0 Update to latest version 797356 Scribunto text/plain -- Module:Excerpt implements the Excerpt template -- Documentation and master version: https://en.wikipedia.org/wiki/Module:Excerpt -- Authors: User:Sophivorus, User:Certes, User:Aidan9382 & others -- License: CC-BY-SA-3.0 local parser = require( 'Module:WikitextParser' ) local yesno = require( 'Module:Yesno' ) local ok, config = pcall( require, 'Module:Excerpt/config' ) if not ok then config = {} end local Excerpt = {} -- Main entry point for templates function Excerpt.main( frame ) -- Make sure the requested page exists and get the wikitext local page = Excerpt.getArg( 1 ) if not page or page == '{{{1}}}' then return Excerpt.getError( 'no-page' ) end local title = mw.title.new( page ) if not title then return Excerpt.getError( 'invalid-title', page ) end local fragment = title.fragment -- save for later if title.isRedirect then title = title.redirectTarget if fragment == "" then fragment = title.fragment -- page merge potential end end if not title.exists then return Excerpt.getError( 'page-not-found', page ) end page = title.prefixedText local wikitext = title:getContent() -- Get the template params and process them local params = { hat = yesno( Excerpt.getArg( 'hat', true ) ), this = Excerpt.getArg( 'this' ), only = Excerpt.getArg( 'only' ), files = Excerpt.getArg( 'files', Excerpt.getArg( 'file' ) ), lists = Excerpt.getArg( 'lists', Excerpt.getArg( 'list' ) ), tables = Excerpt.getArg( 'tables', Excerpt.getArg( 'table' ) ), templates = Excerpt.getArg( 'templates', Excerpt.getArg( 'template' ) ), paragraphs = Excerpt.getArg( 'paragraphs', Excerpt.getArg( 'paragraph' ) ), references = yesno( Excerpt.getArg( 'references', true ) ), subsections = yesno( Excerpt.getArg( 'subsections', false ) ), links = yesno( Excerpt.getArg( 'links', true ) ), bold = yesno( Excerpt.getArg( 'bold', false ) ), briefDates = yesno( Excerpt.getArg( 'briefdates', false ) ), inline = yesno( Excerpt.getArg( 'inline' ) ), quote = yesno( Excerpt.getArg( 'quote' ) ), more = yesno( Excerpt.getArg( 'more' ) ), class = Excerpt.getArg( 'class' ), displayTitle = Excerpt.getArg( 'displaytitle', page ), } -- Make sure the requested section exists and get the excerpt local excerpt local section = Excerpt.getArg( 2, fragment ) section = mw.text.trim( section ) if section == '' then section = nil end if section then excerpt = parser.getSectionTag( wikitext, section ) if not excerpt then if params.subsections then excerpt = parser.getSection( wikitext, section ) else local sections = parser.getSections( wikitext ) excerpt = sections[ section ] end end if not excerpt then return Excerpt.getError( 'section-not-found', section ) end if excerpt == '' then return Excerpt.getError( 'section-empty', section ) end else excerpt = parser.getLead( wikitext ) if excerpt == '' then return Excerpt.getError( 'lead-empty' ) end end -- Remove noinclude bits excerpt = excerpt:gsub( '<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>', '' ) -- Filter various elements from the excerpt excerpt = Excerpt.filterFiles( excerpt, params.files ) excerpt = Excerpt.filterLists( excerpt, params.lists ) excerpt = Excerpt.filterTables( excerpt, params.tables ) excerpt = Excerpt.filterParagraphs( excerpt, params.paragraphs ) -- If no file is found, try to get one from the infobox if ( params.only == 'file' or params.only == 'files' or not params.only and ( not params.files or params.files ~= '0' ) ) -- caller asked for files and not section -- and we're in the lead section and config.captions -- and we have the config option required to try finding files in infoboxes and #parser.getFiles( excerpt ) == 0 -- and there're no files in the excerpt then excerpt = Excerpt.addInfoboxFile( excerpt ) end -- Filter the templates by appending the templates blacklist to the templates filter if config.blacklist then local blacklist = table.concat( config.blacklist, ',' ) if params.templates then if string.sub( params.templates, 1, 1 ) == '-' then params.templates = params.templates .. ',' .. blacklist end else params.templates = '-' .. blacklist end end excerpt = Excerpt.filterTemplates( excerpt, params.templates ) -- Leave only the requested elements if params.only == 'file' or params.only == 'files' then local files = parser.getFiles( excerpt ) excerpt = params.only == 'file' and files[1] or table.concat( files, '\n\n' ) end if params.only == 'list' or params.only == 'lists' then local lists = parser.getLists( excerpt ) excerpt = params.only == 'list' and lists[1] or table.concat( lists, '\n\n' ) end if params.only == 'table' or params.only == 'tables' then local tables = parser.getTables( excerpt ) excerpt = params.only == 'table' and tables[1] or table.concat( tables, '\n\n' ) end if params.only == 'paragraph' or params.only == 'paragraphs' then local paragraphs = parser.getParagraphs( excerpt ) excerpt = params.only == 'paragraph' and paragraphs[1] or table.concat( paragraphs, '\n\n' ) end if params.only == 'template' or params.only == 'templates' then local templates = parser.getTemplates( excerpt ) excerpt = params.only == 'template' and templates[1] or table.concat( templates, '\n\n' ) end -- @todo Make more robust and move downwards if params.briefDates then excerpt = Excerpt.fixDates( excerpt ) end -- Remove unwanted elements excerpt = Excerpt.removeComments( excerpt ) excerpt = Excerpt.removeSelfLinks( excerpt ) excerpt = Excerpt.removeNonFreeFiles( excerpt ) excerpt = Excerpt.removeBehaviorSwitches( excerpt ) -- Fix or remove the references if params.references then excerpt = Excerpt.fixReferences( excerpt, page, wikitext ) else excerpt = Excerpt.removeReferences( excerpt ) end -- Remove wikilinks if not params.links then excerpt = Excerpt.removeLinks( excerpt ) end -- Link the bold text near the start of most leads and then remove it if not section then excerpt = Excerpt.linkBold( excerpt, page ) end if not params.bold then excerpt = Excerpt.removeBold( excerpt ) end -- Remove extra line breaks but leave one before and after so the parser interprets lists, tables, etc. correctly excerpt = excerpt:gsub( '\n\n\n+', '\n\n' ) excerpt = mw.text.trim( excerpt ) excerpt = '\n' .. excerpt .. '\n' -- Remove nested categories excerpt = frame:preprocess( excerpt ) excerpt = Excerpt.removeCategories( excerpt ) -- Add tracking categories if config.categories then excerpt = Excerpt.addTrackingCategories( excerpt ) end -- Build the final output if params.inline then return mw.text.trim( excerpt ) end local tag = params.quote and 'blockquote' or 'div' local block = mw.html.create( tag ):addClass( 'excerpt-block' ):addClass( params.class ) if config.styles then local styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) block:node( styles ) end if params.hat then local hat = Excerpt.getHat( page, section, params ) block:node( hat ) end excerpt = mw.html.create( 'div' ):addClass( 'excerpt' ):wikitext( excerpt ) block:node( excerpt ) if params.more then local more = Excerpt.getReadMore( page, section ) block:node( more ) end return block end -- Filter the files in the given wikitext against the given filter function Excerpt.filterFiles( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local files = parser.getFiles( wikitext ) for index, file in pairs( files ) do local name = parser.getFileName( file ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( name, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( name, filters ) ) then wikitext = Excerpt.removeString( wikitext, file ) end end return wikitext end -- Filter the lists in the given wikitext against the given filter function Excerpt.filterLists( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local lists = parser.getLists( wikitext ) for index, list in pairs( lists ) do if isBlacklist and Excerpt.matchFilter( index, filters ) or not isBlacklist and not Excerpt.matchFilter( index, filters ) then wikitext = Excerpt.removeString( wikitext, list ) end end return wikitext end -- Filter the tables in the given wikitext against the given filter function Excerpt.filterTables( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local tables = parser.getTables( wikitext ) for index, t in pairs( tables ) do local id = string.match( t, '{|[^\n]-id%s*=%s*["\']?([^"\'\n]+)["\']?[^\n]*\n' ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( id, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( id, filters ) ) then wikitext = Excerpt.removeString( wikitext, t ) end end return wikitext end -- Filter the paragraphs in the given wikitext against the given filter function Excerpt.filterParagraphs( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local paragraphs = parser.getParagraphs( wikitext ) for index, paragraph in pairs( paragraphs ) do if isBlacklist and Excerpt.matchFilter( index, filters ) or not isBlacklist and not Excerpt.matchFilter( index, filters ) then wikitext = Excerpt.removeString( wikitext, paragraph ) end end return wikitext end -- Filter the templates in the given wikitext against the given filter function Excerpt.filterTemplates( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local templates = parser.getTemplates( wikitext ) for index, template in pairs( templates ) do local name = parser.getTemplateName( template ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( name, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( name, filters ) ) then wikitext = Excerpt.removeString( wikitext, template ) end end return wikitext end function Excerpt.addInfoboxFile( excerpt ) -- We cannot distinguish the infobox from the other templates, so we search them all local templates = parser.getTemplates( excerpt ) for _, template in pairs( templates ) do local parameters = parser.getTemplateParameters( template ) local file, captions, caption, cssClasses, cssClass for _, pair in pairs( config.captions ) do file = pair[1] file = parameters[file] if file and Excerpt.matchAny( file, '^.*%.', { '[Jj][Pp][Ee]?[Gg]', '[Pp][Nn][Gg]', '[Gg][Ii][Ff]', '[Ss][Vv][Gg]' }, '.*' ) then file = string.match( file, '%[?%[?.-:([^{|]+)%]?%]?' ) or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs( captions ) do if parameters[ p ] then caption = parameters[ p ] break end end -- Check for CSS classes -- We opt to use skin-invert-image instead of skin-invert -- in all other cases, the CSS provided in the infobox is used if pair[3] then cssClasses = pair[3] for _, p in pairs( cssClasses ) do if parameters[ p ] then cssClass = ( parameters[ p ] == 'skin-invert' ) and 'skin-invert-image' or parameters[ p ] break end end end local class = cssClass and ( '|class=' .. cssClass ) or '' return '[[File:' .. file .. class .. '|thumb|' .. ( caption or '' ) .. ']]' .. excerpt end end end return excerpt end function Excerpt.removeNonFreeFiles( wikitext ) local files = parser.getFiles( wikitext ) for _, file in pairs( files ) do local fileName = 'File:' .. parser.getFileName( file ) local fileTitle = mw.title.new( fileName ) if fileTitle then local fileDescription = fileTitle:getContent() if not fileDescription or fileDescription == '' then local frame = mw.getCurrentFrame() fileDescription = frame:preprocess( '{{' .. fileName .. '}}' ) -- try Commons end if fileDescription and string.match( fileDescription, '[Nn]on%-free' ) then wikitext = Excerpt.removeString( wikitext, file ) end end end return wikitext end function Excerpt.getHat( page, section, params ) local hat -- Build the text if params.this then hat = params.this elseif params.quote then hat = Excerpt.getMessage( 'this' ) elseif params.only then hat = Excerpt.getMessage( params.only ) else hat = Excerpt.getMessage( 'section' ) end hat = hat .. ' ' .. Excerpt.getMessage( 'excerpt' ) -- Build the link if section then hat = hat .. ' [[:' .. page .. '#' .. mw.uri.anchorEncode( section ) .. '|' .. params.displayTitle .. ' § ' .. section:gsub( '%[%[([^]|]+)|?[^]]*%]%]', '%1' ) .. ']].' -- remove nested links else hat = hat .. ' [[:' .. page .. '|' .. params.displayTitle .. ']].' end -- Build the edit link local title = mw.title.new( page ) local editUrl = title:fullUrl( 'action=edit' ) hat = hat .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. editUrl .. ' ' .. mw.message.new( 'editsection' ):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' if config.hat then local frame = mw.getCurrentFrame() hat = config.hat .. hat .. '}}' hat = frame:preprocess( hat ) else hat = mw.html.create( 'div' ):addClass( 'dablink excerpt-hat' ):wikitext( hat ) end return hat end function Excerpt.getReadMore( page, section ) local link = "'''[[" .. page if section then link = link .. '#' .. section end local text = Excerpt.getMessage( 'more' ) link = link .. '|' .. text .. "]]'''" link = mw.html.create( 'div' ):addClass( 'noprint excerpt-more' ):wikitext( link ) return link end -- Fix birth and death dates, but only in the first paragraph -- @todo Use parser.getParagraphs() to get the first paragraph function Excerpt.fixDates( excerpt ) local start = 1 -- skip initial templates local s local e = 0 repeat start = e + 1 s, e = mw.ustring.find( excerpt, '%s*%b{}%s*', start ) until not s or s > start s, e = mw.ustring.find( excerpt, '%b()', start ) -- get (...), which may be (year–year) if s and s < start + 100 then -- look only near the start local excerptStart = mw.ustring.sub( excerpt, s, e ) local year1, conjunction, year2 = string.match( excerptStart, '(%d%d%d+)(.-)(%d%d%d+)' ) if year1 and year2 and ( string.match( conjunction, '[%-–—]' ) or string.match( conjunction, '{{%s*[sS]nd%s*}}' ) ) then local y1 = tonumber( year1 ) local y2 = tonumber( year2 ) if y2 > y1 and y2 < y1 + 125 and y1 <= tonumber( os.date( '%Y' ) ) then excerpt = mw.ustring.sub( excerpt, 1, s ) .. year1 .. '–' .. year2 .. mw.ustring.sub( excerpt, e ) end end end return excerpt end -- Replace the first call to each reference defined outside of the excerpt for the full reference, to prevent undefined references -- Then prefix the page title to the reference names to prevent conflicts -- that is, replace <ref name="Foo"> for <ref name="Title of the article Foo"> -- and also <ref name="Foo" /> for <ref name="Title of the article Foo" /> -- also remove reference groups: <ref name="Foo" group="Bar"> for <ref name="Title of the article Foo"> -- and <ref group="Bar"> for <ref> -- @todo The current regex may fail in cases with both kinds of quotes, like <ref name="Darwin's book"> function Excerpt.fixReferences( excerpt, page, wikitext ) local references = parser.getReferences( excerpt ) local fixed = {} for _, reference in pairs( references ) do local name = parser.getTagAttribute( reference, 'name' ) if not fixed[ name ] then -- fix each reference only once local content = parser.getTagContent( reference ) if not content then -- reference is self-closing local full = parser.getReference( excerpt, name ) if not full then -- the reference is not defined in the excerpt full = parser.getReference( wikitext, name ) if full then excerpt = excerpt:gsub( Excerpt.escapeString( reference ), Excerpt.escapeString( full ), 1 ) end table.insert( fixed, name ) end end end end -- Prepend the page title to the reference names to prevent conflicts with other references in the transcluding page excerpt = excerpt:gsub( '< *[Rr][Ee][Ff][^>]*name *= *["\']?([^"\'>/]+)["\']?[^>/]*(/?) *>', '<ref name="' .. page:gsub( '"', '' ) .. ' %1"%2>' ) -- Remove reference groups because they don't apply to the transcluding page excerpt = excerpt:gsub( '< *[Rr][Ee][Ff] *group *= *["\']?[^"\'>/]+["\'] *>', '<ref>' ) return excerpt end function Excerpt.removeReferences( excerpt ) local references = parser.getReferences( excerpt ) for _, reference in pairs( references ) do excerpt = Excerpt.removeString( excerpt, reference ) end return excerpt end function Excerpt.removeCategories( excerpt ) local categories = parser.getCategories( excerpt ) for _, category in pairs( categories ) do excerpt = Excerpt.removeString( excerpt, category ) end return excerpt end function Excerpt.removeBehaviorSwitches( excerpt ) return excerpt:gsub( '__[A-Z]+__', '' ) end function Excerpt.removeComments( excerpt ) return excerpt:gsub( '<!%-%-.-%-%->', '' ) end function Excerpt.removeBold( excerpt ) return excerpt:gsub( "'''", '' ) end function Excerpt.removeLinks( excerpt ) local links = parser.getLinks( excerpt ) for _, link in pairs( links ) do excerpt = Excerpt.removeString( excerpt, link ) end return excerpt end -- @todo Use parser.getLinks function Excerpt.removeSelfLinks( excerpt, page ) local lang = mw.language.getContentLanguage() local page = Excerpt.escapeString( mw.title.getCurrentTitle().prefixedText ) local ucpage = lang:ucfirst( page ) local lcpage = lang:lcfirst( page ) excerpt = excerpt :gsub( '%[%[(' .. ucpage .. ')%]%]', '%1' ) :gsub( '%[%[(' .. lcpage .. ')%]%]', '%1' ) :gsub( '%[%[' .. ucpage .. '|([^]]+)%]%]', '%1' ) :gsub( '%[%[' .. lcpage .. '|([^]]+)%]%]', '%1' ) return excerpt end -- Replace the bold title or synonym near the start of the page by a link to the page function Excerpt.linkBold( excerpt, page ) local lang = mw.language.getContentLanguage() local position = mw.ustring.find( excerpt, "'''" .. lang:ucfirst( page ) .. "'''", 1, true ) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find( excerpt, "'''" .. lang:lcfirst( page ) .. "'''", 1, true ) -- plain search: special characters in page represent themselves if position then local length = mw.ustring.len( page ) excerpt = mw.ustring.sub( excerpt, 1, position + 2 ) .. '[[' .. mw.ustring.sub( excerpt, position + 3, position + length + 2 ) .. ']]' .. mw.ustring.sub( excerpt, position + length + 3, -1 ) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) excerpt = mw.ustring.gsub( excerpt, "()'''(.-'*)'''", function ( a, b ) if not mw.ustring.find( b, '%[' ) and not mw.ustring.find( b, '%{' ) then -- if not wikilinked or some weird template return "'''[[" .. page .. '|' .. b .. "]]'''" -- replace '''Foo''' by '''[[page|Foo]]''' else return nil -- instruct gsub to make no change end end, 1 ) -- terminates the anonymous replacement function passed to gsub end return excerpt end function Excerpt.addTrackingCategories( excerpt ) local currentTitle = mw.title.getCurrentTitle() local contentCategory = config.categories.content if contentCategory and currentTitle.isContentPage then excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ currentTitle.namespace ] if namespaceCategory then excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end return excerpt end -- Helper method to match from a list of regular expressions -- Like so: match pre..list[1]..post or pre..list[2]..post or ... function Excerpt.matchAny( text, pre, list, post, init ) local match = {} for i = 1, #list do match = { mw.ustring.match( text, pre .. list[ i ] .. post, init ) } if match[1] then return unpack( match ) end end return nil end -- Helper function to get arguments -- args from Lua calls have priority over parent args from template function Excerpt.getArg( key, default ) local frame = mw.getCurrentFrame() for k, value in pairs( frame:getParent().args ) do if k == key and mw.text.trim( value ) ~= '' then return value end end for k, value in pairs( frame.args ) do if k == key and mw.text.trim( value ) ~= '' then return value end end return default end -- Helper method to get an error message -- This method also categorizes the current page in one of the configured error categories function Excerpt.getError( key, value ) local message = Excerpt.getMessage( 'error-' .. key, value ) local markup = mw.html.create( 'div' ):addClass( 'error' ):wikitext( message ) if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then markup:node( '[[Category:' .. config.categories.errors .. ']]' ) end return markup end -- Helper method to get a localized message -- This method uses Module:TNT to get localized messages from https://commons.wikimedia.org/wiki/Data:I18n/Module:Excerpt.tab -- If Module:TNT is not available or the localized message does not exist, the key is returned instead function Excerpt.getMessage( key, value ) local ok, TNT = pcall( require, 'Module:TNT' ) if not ok then return key end local ok2, message = pcall( TNT.format, 'I18n/Module:Excerpt.tab', key, value ) if not ok2 then return key end return message end -- Helper method to escape a string for use in regexes function Excerpt.escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Helper method to remove a string from a text -- @param text Text from where to remove the string -- @param str String to remove -- @return The given text with the string removed function Excerpt.removeString( text, str ) local pattern = Excerpt.escapeString( str ) if #pattern > 9999 then -- strings longer than 10000 bytes can't be put into regexes pattern = Excerpt.escapeString( mw.ustring.sub( str, 1, 999 ) ) .. '.-' .. Excerpt.escapeString( mw.ustring.sub( str, -999 ) ) end return text:gsub( pattern, '' ) end -- Helper method to convert a comma-separated list of numbers or min-max ranges into a list of booleans -- @param filter Required. Comma-separated list of numbers or min-max ranges, for example '1,3-5' -- @return Map from integers to booleans, for example {1=true,2=false,3=true,4=true,5=true} -- @return Boolean indicating whether the filters should be treated as a blacklist or not -- @note Merging this into matchFilter is possible, but way too inefficient function Excerpt.parseFilter( filter ) local filters = {} local isBlacklist = false if string.sub( filter, 1, 1 ) == '-' then isBlacklist = true filter = string.sub( filter, 2 ) end local values = mw.text.split( filter, ',' ) -- split values: '1,3-5' to {'1','3-5'} for _, value in pairs( values ) do value = mw.text.trim( value ) local min, max = mw.ustring.match( value, '^(%d+)%s*[-–—]%s*(%d+)$' ) -- '3-5' to min=3 max=5 if not max then min, max = string.match( value, '^((%d+))$' ) end -- '1' to min=1 max=1 if max then for i = min, max do filters[ i ] = true end else filters[ value ] = true -- if we reach this point, the string had the form 'a,b,c' rather than '1,2,3' end end local filter = {cache = {}, terms = filters} return filter, isBlacklist end -- Helper function to see if a value matches any of the given filters function Excerpt.matchFilter( value, filter ) if type(value) == "number" then return filter.terms[value] else local cached = filter.cache[value] if cached ~= nil then return cached end local lang = mw.language.getContentLanguage() local lcvalue = lang:lcfirst(value) local ucvalue = lang:ucfirst(value) for term in pairs( filter.terms ) do if value == tostring(term) or type(term) == "string" and ( lcvalue == term or ucvalue == term or mw.ustring.match( value, term ) ) then filter.cache[value] = true return true end end filter.cache[value] = false end end return Excerpt jgd9kvsiee9c9ouo5xxzek9fb6zwqe8 797357 797356 2025-10-06T13:09:35Z en>Sophivorus 0 Make matchFilter account for nil filters, use WikitextParser to get table ids, and remove unnecessary code 797357 Scribunto text/plain -- Module:Excerpt implements the Excerpt template -- Documentation and master version: https://en.wikipedia.org/wiki/Module:Excerpt -- Authors: User:Sophivorus, User:Certes, User:Aidan9382 & others -- License: CC-BY-SA-3.0 local parser = require( 'Module:WikitextParser' ) local yesno = require( 'Module:Yesno' ) local ok, config = pcall( require, 'Module:Excerpt/config' ) if not ok then config = {} end local Excerpt = {} -- Main entry point for templates function Excerpt.main( frame ) -- Make sure the requested page exists and get the wikitext local page = Excerpt.getArg( 1 ) if not page or page == '{{{1}}}' then return Excerpt.getError( 'no-page' ) end local title = mw.title.new( page ) if not title then return Excerpt.getError( 'invalid-title', page ) end local fragment = title.fragment -- save for later if title.isRedirect then title = title.redirectTarget if fragment == "" then fragment = title.fragment -- page merge potential end end if not title.exists then return Excerpt.getError( 'page-not-found', page ) end page = title.prefixedText local wikitext = title:getContent() -- Get the template params and process them local params = { hat = yesno( Excerpt.getArg( 'hat', true ) ), this = Excerpt.getArg( 'this' ), only = Excerpt.getArg( 'only' ), files = Excerpt.getArg( 'files', Excerpt.getArg( 'file' ) ), lists = Excerpt.getArg( 'lists', Excerpt.getArg( 'list' ) ), tables = Excerpt.getArg( 'tables', Excerpt.getArg( 'table' ) ), templates = Excerpt.getArg( 'templates', Excerpt.getArg( 'template' ) ), paragraphs = Excerpt.getArg( 'paragraphs', Excerpt.getArg( 'paragraph' ) ), references = yesno( Excerpt.getArg( 'references', true ) ), subsections = yesno( Excerpt.getArg( 'subsections', false ) ), links = yesno( Excerpt.getArg( 'links', true ) ), bold = yesno( Excerpt.getArg( 'bold', false ) ), briefDates = yesno( Excerpt.getArg( 'briefdates', false ) ), inline = yesno( Excerpt.getArg( 'inline' ) ), quote = yesno( Excerpt.getArg( 'quote' ) ), more = yesno( Excerpt.getArg( 'more' ) ), class = Excerpt.getArg( 'class' ), displayTitle = Excerpt.getArg( 'displaytitle', page ), } -- Make sure the requested section exists and get the excerpt local excerpt local section = Excerpt.getArg( 2, fragment ) section = mw.text.trim( section ) if section == '' then section = nil end if section then excerpt = parser.getSectionTag( wikitext, section ) if not excerpt then if params.subsections then excerpt = parser.getSection( wikitext, section ) else local sections = parser.getSections( wikitext ) excerpt = sections[ section ] end end if not excerpt then return Excerpt.getError( 'section-not-found', section ) end if excerpt == '' then return Excerpt.getError( 'section-empty', section ) end else excerpt = parser.getLead( wikitext ) if excerpt == '' then return Excerpt.getError( 'lead-empty' ) end end -- Remove noinclude bits excerpt = excerpt:gsub( '<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>', '' ) -- Filter various elements from the excerpt excerpt = Excerpt.filterFiles( excerpt, params.files ) excerpt = Excerpt.filterLists( excerpt, params.lists ) excerpt = Excerpt.filterTables( excerpt, params.tables ) excerpt = Excerpt.filterParagraphs( excerpt, params.paragraphs ) -- If no file is found, try to get one from the infobox if ( params.only == 'file' or params.only == 'files' or not params.only and ( not params.files or params.files ~= '0' ) ) -- caller asked for files and not section -- and we're in the lead section and config.captions -- and we have the config option required to try finding files in infoboxes and #parser.getFiles( excerpt ) == 0 -- and there're no files in the excerpt then excerpt = Excerpt.addInfoboxFile( excerpt ) end -- Filter the templates by appending the templates blacklist to the templates filter if config.blacklist then local blacklist = table.concat( config.blacklist, ',' ) if params.templates then if string.sub( params.templates, 1, 1 ) == '-' then params.templates = params.templates .. ',' .. blacklist end else params.templates = '-' .. blacklist end end excerpt = Excerpt.filterTemplates( excerpt, params.templates ) -- Leave only the requested elements if params.only == 'file' or params.only == 'files' then local files = parser.getFiles( excerpt ) excerpt = params.only == 'file' and files[1] or table.concat( files, '\n\n' ) end if params.only == 'list' or params.only == 'lists' then local lists = parser.getLists( excerpt ) excerpt = params.only == 'list' and lists[1] or table.concat( lists, '\n\n' ) end if params.only == 'table' or params.only == 'tables' then local tables = parser.getTables( excerpt ) excerpt = params.only == 'table' and tables[1] or table.concat( tables, '\n\n' ) end if params.only == 'paragraph' or params.only == 'paragraphs' then local paragraphs = parser.getParagraphs( excerpt ) excerpt = params.only == 'paragraph' and paragraphs[1] or table.concat( paragraphs, '\n\n' ) end if params.only == 'template' or params.only == 'templates' then local templates = parser.getTemplates( excerpt ) excerpt = params.only == 'template' and templates[1] or table.concat( templates, '\n\n' ) end -- @todo Make more robust and move downwards if params.briefDates then excerpt = Excerpt.fixDates( excerpt ) end -- Remove unwanted elements excerpt = Excerpt.removeComments( excerpt ) excerpt = Excerpt.removeSelfLinks( excerpt ) excerpt = Excerpt.removeNonFreeFiles( excerpt ) excerpt = Excerpt.removeBehaviorSwitches( excerpt ) -- Fix or remove the references if params.references then excerpt = Excerpt.fixReferences( excerpt, page, wikitext ) else excerpt = Excerpt.removeReferences( excerpt ) end -- Remove wikilinks if not params.links then excerpt = Excerpt.removeLinks( excerpt ) end -- Link the bold text near the start of most leads and then remove it if not section then excerpt = Excerpt.linkBold( excerpt, page ) end if not params.bold then excerpt = Excerpt.removeBold( excerpt ) end -- Remove extra line breaks but leave one before and after so the parser interprets lists, tables, etc. correctly excerpt = excerpt:gsub( '\n\n\n+', '\n\n' ) excerpt = mw.text.trim( excerpt ) excerpt = '\n' .. excerpt .. '\n' -- Remove nested categories excerpt = frame:preprocess( excerpt ) excerpt = Excerpt.removeCategories( excerpt ) -- Add tracking categories if config.categories then excerpt = Excerpt.addTrackingCategories( excerpt ) end -- Build the final output if params.inline then return mw.text.trim( excerpt ) end local tag = params.quote and 'blockquote' or 'div' local block = mw.html.create( tag ):addClass( 'excerpt-block' ):addClass( params.class ) if config.styles then local styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) block:node( styles ) end if params.hat then local hat = Excerpt.getHat( page, section, params ) block:node( hat ) end excerpt = mw.html.create( 'div' ):addClass( 'excerpt' ):wikitext( excerpt ) block:node( excerpt ) if params.more then local more = Excerpt.getReadMore( page, section ) block:node( more ) end return block end -- Filter the files in the given wikitext against the given filter function Excerpt.filterFiles( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local files = parser.getFiles( wikitext ) for index, file in pairs( files ) do local name = parser.getFileName( file ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( name, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( name, filters ) ) then wikitext = Excerpt.removeString( wikitext, file ) end end return wikitext end -- Filter the lists in the given wikitext against the given filter function Excerpt.filterLists( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local lists = parser.getLists( wikitext ) for index, list in pairs( lists ) do if isBlacklist and Excerpt.matchFilter( index, filters ) or not isBlacklist and not Excerpt.matchFilter( index, filters ) then wikitext = Excerpt.removeString( wikitext, list ) end end return wikitext end -- Filter the tables in the given wikitext against the given filter function Excerpt.filterTables( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local tables = parser.getTables( wikitext ) for index, tableWikitext in pairs( tables ) do local id = parser.getTableAttribute( tableWikitext, 'id' ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( id, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( id, filters ) ) then wikitext = Excerpt.removeString( wikitext, tableWikitext ) end end return wikitext end -- Filter the paragraphs in the given wikitext against the given filter function Excerpt.filterParagraphs( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local paragraphs = parser.getParagraphs( wikitext ) for index, paragraph in pairs( paragraphs ) do if isBlacklist and Excerpt.matchFilter( index, filters ) or not isBlacklist and not Excerpt.matchFilter( index, filters ) then wikitext = Excerpt.removeString( wikitext, paragraph ) end end return wikitext end -- Filter the templates in the given wikitext against the given filter function Excerpt.filterTemplates( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local templates = parser.getTemplates( wikitext ) for index, template in pairs( templates ) do local name = parser.getTemplateName( template ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( name, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( name, filters ) ) then wikitext = Excerpt.removeString( wikitext, template ) end end return wikitext end function Excerpt.addInfoboxFile( excerpt ) -- We cannot distinguish the infobox from the other templates, so we search them all local templates = parser.getTemplates( excerpt ) for _, template in pairs( templates ) do local parameters = parser.getTemplateParameters( template ) local file, captions, caption, cssClasses, cssClass for _, pair in pairs( config.captions ) do file = pair[1] file = parameters[file] if file and Excerpt.matchAny( file, '^.*%.', { '[Jj][Pp][Ee]?[Gg]', '[Pp][Nn][Gg]', '[Gg][Ii][Ff]', '[Ss][Vv][Gg]' }, '.*' ) then file = string.match( file, '%[?%[?.-:([^{|]+)%]?%]?' ) or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs( captions ) do if parameters[ p ] then caption = parameters[ p ] break end end -- Check for CSS classes -- We opt to use skin-invert-image instead of skin-invert -- in all other cases, the CSS provided in the infobox is used if pair[3] then cssClasses = pair[3] for _, p in pairs( cssClasses ) do if parameters[ p ] then cssClass = ( parameters[ p ] == 'skin-invert' ) and 'skin-invert-image' or parameters[ p ] break end end end local class = cssClass and ( '|class=' .. cssClass ) or '' return '[[File:' .. file .. class .. '|thumb|' .. ( caption or '' ) .. ']]' .. excerpt end end end return excerpt end function Excerpt.removeNonFreeFiles( wikitext ) local files = parser.getFiles( wikitext ) for _, file in pairs( files ) do local fileName = 'File:' .. parser.getFileName( file ) local fileTitle = mw.title.new( fileName ) if fileTitle then local fileDescription = fileTitle:getContent() if not fileDescription or fileDescription == '' then local frame = mw.getCurrentFrame() fileDescription = frame:preprocess( '{{' .. fileName .. '}}' ) -- try Commons end if fileDescription and string.match( fileDescription, '[Nn]on%-free' ) then wikitext = Excerpt.removeString( wikitext, file ) end end end return wikitext end function Excerpt.getHat( page, section, params ) local hat -- Build the text if params.this then hat = params.this elseif params.quote then hat = Excerpt.getMessage( 'this' ) elseif params.only then hat = Excerpt.getMessage( params.only ) else hat = Excerpt.getMessage( 'section' ) end hat = hat .. ' ' .. Excerpt.getMessage( 'excerpt' ) -- Build the link if section then hat = hat .. ' [[:' .. page .. '#' .. mw.uri.anchorEncode( section ) .. '|' .. params.displayTitle .. ' § ' .. section:gsub( '%[%[([^]|]+)|?[^]]*%]%]', '%1' ) .. ']].' -- remove nested links else hat = hat .. ' [[:' .. page .. '|' .. params.displayTitle .. ']].' end -- Build the edit link local title = mw.title.new( page ) local editUrl = title:fullUrl( 'action=edit' ) hat = hat .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. editUrl .. ' ' .. mw.message.new( 'editsection' ):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' if config.hat then local frame = mw.getCurrentFrame() hat = config.hat .. hat .. '}}' hat = frame:preprocess( hat ) else hat = mw.html.create( 'div' ):addClass( 'dablink excerpt-hat' ):wikitext( hat ) end return hat end function Excerpt.getReadMore( page, section ) local link = "'''[[" .. page if section then link = link .. '#' .. section end local text = Excerpt.getMessage( 'more' ) link = link .. '|' .. text .. "]]'''" link = mw.html.create( 'div' ):addClass( 'noprint excerpt-more' ):wikitext( link ) return link end -- Fix birth and death dates, but only in the first paragraph -- @todo Use parser.getParagraphs() to get the first paragraph function Excerpt.fixDates( excerpt ) local start = 1 -- skip initial templates local s local e = 0 repeat start = e + 1 s, e = mw.ustring.find( excerpt, '%s*%b{}%s*', start ) until not s or s > start s, e = mw.ustring.find( excerpt, '%b()', start ) -- get (...), which may be (year–year) if s and s < start + 100 then -- look only near the start local excerptStart = mw.ustring.sub( excerpt, s, e ) local year1, conjunction, year2 = string.match( excerptStart, '(%d%d%d+)(.-)(%d%d%d+)' ) if year1 and year2 and ( string.match( conjunction, '[%-–—]' ) or string.match( conjunction, '{{%s*[sS]nd%s*}}' ) ) then local y1 = tonumber( year1 ) local y2 = tonumber( year2 ) if y2 > y1 and y2 < y1 + 125 and y1 <= tonumber( os.date( '%Y' ) ) then excerpt = mw.ustring.sub( excerpt, 1, s ) .. year1 .. '–' .. year2 .. mw.ustring.sub( excerpt, e ) end end end return excerpt end -- Replace the first call to each reference defined outside of the excerpt for the full reference, to prevent undefined references -- Then prefix the page title to the reference names to prevent conflicts -- that is, replace <ref name="Foo"> for <ref name="Title of the article Foo"> -- and also <ref name="Foo" /> for <ref name="Title of the article Foo" /> -- also remove reference groups: <ref name="Foo" group="Bar"> for <ref name="Title of the article Foo"> -- and <ref group="Bar"> for <ref> -- @todo The current regex may fail in cases with both kinds of quotes, like <ref name="Darwin's book"> function Excerpt.fixReferences( excerpt, page, wikitext ) local references = parser.getReferences( excerpt ) local fixed = {} for _, reference in pairs( references ) do local name = parser.getTagAttribute( reference, 'name' ) if not fixed[ name ] then -- fix each reference only once local content = parser.getTagContent( reference ) if not content then -- reference is self-closing local full = parser.getReference( excerpt, name ) if not full then -- the reference is not defined in the excerpt full = parser.getReference( wikitext, name ) if full then excerpt = excerpt:gsub( Excerpt.escapeString( reference ), Excerpt.escapeString( full ), 1 ) end table.insert( fixed, name ) end end end end -- Prepend the page title to the reference names to prevent conflicts with other references in the transcluding page excerpt = excerpt:gsub( '< *[Rr][Ee][Ff][^>]*name *= *["\']?([^"\'>/]+)["\']?[^>/]*(/?) *>', '<ref name="' .. page:gsub( '"', '' ) .. ' %1"%2>' ) -- Remove reference groups because they don't apply to the transcluding page excerpt = excerpt:gsub( '< *[Rr][Ee][Ff] *group *= *["\']?[^"\'>/]+["\'] *>', '<ref>' ) return excerpt end function Excerpt.removeReferences( excerpt ) local references = parser.getReferences( excerpt ) for _, reference in pairs( references ) do excerpt = Excerpt.removeString( excerpt, reference ) end return excerpt end function Excerpt.removeCategories( excerpt ) local categories = parser.getCategories( excerpt ) for _, category in pairs( categories ) do excerpt = Excerpt.removeString( excerpt, category ) end return excerpt end function Excerpt.removeBehaviorSwitches( excerpt ) return excerpt:gsub( '__[A-Z]+__', '' ) end function Excerpt.removeComments( excerpt ) return excerpt:gsub( '<!%-%-.-%-%->', '' ) end function Excerpt.removeBold( excerpt ) return excerpt:gsub( "'''", '' ) end function Excerpt.removeLinks( excerpt ) local links = parser.getLinks( excerpt ) for _, link in pairs( links ) do excerpt = Excerpt.removeString( excerpt, link ) end return excerpt end -- @todo Use parser.getLinks function Excerpt.removeSelfLinks( excerpt ) local lang = mw.language.getContentLanguage() local page = Excerpt.escapeString( mw.title.getCurrentTitle().prefixedText ) local ucpage = lang:ucfirst( page ) local lcpage = lang:lcfirst( page ) excerpt = excerpt :gsub( '%[%[(' .. ucpage .. ')%]%]', '%1' ) :gsub( '%[%[(' .. lcpage .. ')%]%]', '%1' ) :gsub( '%[%[' .. ucpage .. '|([^]]+)%]%]', '%1' ) :gsub( '%[%[' .. lcpage .. '|([^]]+)%]%]', '%1' ) return excerpt end -- Replace the bold title or synonym near the start of the page by a link to the page function Excerpt.linkBold( excerpt, page ) local lang = mw.language.getContentLanguage() local position = mw.ustring.find( excerpt, "'''" .. lang:ucfirst( page ) .. "'''", 1, true ) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find( excerpt, "'''" .. lang:lcfirst( page ) .. "'''", 1, true ) -- plain search: special characters in page represent themselves if position then local length = mw.ustring.len( page ) excerpt = mw.ustring.sub( excerpt, 1, position + 2 ) .. '[[' .. mw.ustring.sub( excerpt, position + 3, position + length + 2 ) .. ']]' .. mw.ustring.sub( excerpt, position + length + 3, -1 ) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) excerpt = mw.ustring.gsub( excerpt, "'''(.-'*)'''", function ( text ) if not string.find( text, '%[' ) and not string.find( text, '%{' ) then -- if not wikilinked or some weird template return "'''[[" .. page .. '|' .. text .. "]]'''" -- replace '''Foo''' by '''[[page|Foo]]''' else return nil -- instruct gsub to make no change end end, 1 ) -- terminates the anonymous replacement function passed to gsub end return excerpt end function Excerpt.addTrackingCategories( excerpt ) local currentTitle = mw.title.getCurrentTitle() local contentCategory = config.categories.content if contentCategory and currentTitle.isContentPage then excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ currentTitle.namespace ] if namespaceCategory then excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end return excerpt end -- Helper method to match from a list of regular expressions -- Like so: match pre..list[1]..post or pre..list[2]..post or ... function Excerpt.matchAny( text, pre, list, post, init ) local match = {} for i = 1, #list do match = { mw.ustring.match( text, pre .. list[ i ] .. post, init ) } if match[1] then return unpack( match ) end end return nil end -- Helper function to get arguments -- args from Lua calls have priority over parent args from template function Excerpt.getArg( key, default ) local frame = mw.getCurrentFrame() for k, value in pairs( frame:getParent().args ) do if k == key and mw.text.trim( value ) ~= '' then return value end end for k, value in pairs( frame.args ) do if k == key and mw.text.trim( value ) ~= '' then return value end end return default end -- Helper method to get an error message -- This method also categorizes the current page in one of the configured error categories function Excerpt.getError( key, value ) local message = Excerpt.getMessage( 'error-' .. key, value ) local markup = mw.html.create( 'div' ):addClass( 'error' ):wikitext( message ) if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then markup:node( '[[Category:' .. config.categories.errors .. ']]' ) end return markup end -- Helper method to get a localized message -- This method uses Module:TNT to get localized messages from https://commons.wikimedia.org/wiki/Data:I18n/Module:Excerpt.tab -- If Module:TNT is not available or the localized message does not exist, the key is returned instead function Excerpt.getMessage( key, value ) local ok, TNT = pcall( require, 'Module:TNT' ) if not ok then return key end local ok2, message = pcall( TNT.format, 'I18n/Module:Excerpt.tab', key, value ) if not ok2 then return key end return message end -- Helper method to escape a string for use in regexes function Excerpt.escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Helper method to remove a string from a text -- @param text Text from where to remove the string -- @param str String to remove -- @return The given text with the string removed function Excerpt.removeString( text, str ) local pattern = Excerpt.escapeString( str ) if #pattern > 9999 then -- strings longer than 10000 bytes can't be put into regexes pattern = Excerpt.escapeString( mw.ustring.sub( str, 1, 999 ) ) .. '.-' .. Excerpt.escapeString( mw.ustring.sub( str, -999 ) ) end return text:gsub( pattern, '' ) end -- Helper method to convert a comma-separated list of numbers or min-max ranges into a list of booleans -- @param filter Required. Comma-separated list of numbers or min-max ranges, for example '1,3-5' -- @return Map from integers to booleans, for example {1=true,2=false,3=true,4=true,5=true} -- @return Boolean indicating whether the filters should be treated as a blacklist or not -- @note Merging this into matchFilter is possible, but way too inefficient function Excerpt.parseFilter( filter ) local filters = {} local isBlacklist = false if string.sub( filter, 1, 1 ) == '-' then isBlacklist = true filter = string.sub( filter, 2 ) end local values = mw.text.split( filter, ',' ) -- split values: '1,3-5' to {'1','3-5'} for _, value in pairs( values ) do value = mw.text.trim( value ) local min, max = mw.ustring.match( value, '^(%d+)%s*[-–—]%s*(%d+)$' ) -- '3-5' to min=3 max=5 if not max then min, max = string.match( value, '^((%d+))$' ) end -- '1' to min=1 max=1 if max then for i = min, max do filters[ i ] = true end else filters[ value ] = true -- if we reach this point, the string had the form 'a,b,c' rather than '1,2,3' end end local filter = {cache = {}, terms = filters} return filter, isBlacklist end -- Helper function to see if a value matches any of the given filters function Excerpt.matchFilter( value, filter ) if value == nil then return false elseif type(value) == "number" then return filter.terms[value] else local cached = filter.cache[value] if cached ~= nil then return cached end local lang = mw.language.getContentLanguage() local lcvalue = lang:lcfirst(value) local ucvalue = lang:ucfirst(value) for term in pairs( filter.terms ) do if value == tostring(term) or type(term) == "string" and ( lcvalue == term or ucvalue == term or mw.ustring.match( value, term ) ) then filter.cache[value] = true return true end end filter.cache[value] = false end end return Excerpt rnq76uv86a6rsvb7gbdoa5jldh3wc2d 797358 797357 2025-10-10T12:22:12Z en>Sophivorus 0 Add param to skip tracking categories 797358 Scribunto text/plain -- Module:Excerpt implements the Excerpt template -- Documentation and master version: https://en.wikipedia.org/wiki/Module:Excerpt -- Authors: User:Sophivorus, User:Certes, User:Aidan9382 & others -- License: CC-BY-SA-3.0 local parser = require( 'Module:WikitextParser' ) local yesno = require( 'Module:Yesno' ) local ok, config = pcall( require, 'Module:Excerpt/config' ) if not ok then config = {} end local Excerpt = {} -- Main entry point for templates function Excerpt.main( frame ) -- Make sure the requested page exists and get the wikitext local page = Excerpt.getArg( 1 ) if not page or page == '{{{1}}}' then return Excerpt.getError( 'no-page' ) end local title = mw.title.new( page ) if not title then return Excerpt.getError( 'invalid-title', page ) end local fragment = title.fragment -- save for later if title.isRedirect then title = title.redirectTarget if fragment == "" then fragment = title.fragment -- page merge potential end end if not title.exists then return Excerpt.getError( 'page-not-found', page ) end page = title.prefixedText local wikitext = title:getContent() -- Get the template params and process them local params = { hat = yesno( Excerpt.getArg( 'hat', true ) ), this = Excerpt.getArg( 'this' ), only = Excerpt.getArg( 'only' ), files = Excerpt.getArg( 'files', Excerpt.getArg( 'file' ) ), lists = Excerpt.getArg( 'lists', Excerpt.getArg( 'list' ) ), tables = Excerpt.getArg( 'tables', Excerpt.getArg( 'table' ) ), templates = Excerpt.getArg( 'templates', Excerpt.getArg( 'template' ) ), paragraphs = Excerpt.getArg( 'paragraphs', Excerpt.getArg( 'paragraph' ) ), references = yesno( Excerpt.getArg( 'references', true ) ), subsections = yesno( Excerpt.getArg( 'subsections', false ) ), links = yesno( Excerpt.getArg( 'links', true ) ), bold = yesno( Excerpt.getArg( 'bold', false ) ), briefDates = yesno( Excerpt.getArg( 'briefdates', false ) ), inline = yesno( Excerpt.getArg( 'inline' ) ), quote = yesno( Excerpt.getArg( 'quote' ) ), more = yesno( Excerpt.getArg( 'more' ) ), class = Excerpt.getArg( 'class' ), trackingCategories = yesno( Excerpt.getArg( 'trackingCategories', true ) ), displayTitle = Excerpt.getArg( 'displaytitle', page ), } -- Make sure the requested section exists and get the excerpt local excerpt local section = Excerpt.getArg( 2, fragment ) section = mw.text.trim( section ) if section == '' then section = nil end if section then excerpt = parser.getSectionTag( wikitext, section ) if not excerpt then if params.subsections then excerpt = parser.getSection( wikitext, section ) else local sections = parser.getSections( wikitext ) excerpt = sections[ section ] end end if not excerpt then return Excerpt.getError( 'section-not-found', section ) end if excerpt == '' then return Excerpt.getError( 'section-empty', section ) end else excerpt = parser.getLead( wikitext ) if excerpt == '' then return Excerpt.getError( 'lead-empty' ) end end -- Remove noinclude bits excerpt = excerpt:gsub( '<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>', '' ) -- Filter various elements from the excerpt excerpt = Excerpt.filterFiles( excerpt, params.files ) excerpt = Excerpt.filterLists( excerpt, params.lists ) excerpt = Excerpt.filterTables( excerpt, params.tables ) excerpt = Excerpt.filterParagraphs( excerpt, params.paragraphs ) -- If no file is found, try to get one from the infobox if ( params.only == 'file' or params.only == 'files' or not params.only and ( not params.files or params.files ~= '0' ) ) -- caller asked for files and not section -- and we're in the lead section and config.captions -- and we have the config option required to try finding files in infoboxes and #parser.getFiles( excerpt ) == 0 -- and there're no files in the excerpt then excerpt = Excerpt.addInfoboxFile( excerpt ) end -- Filter the templates by appending the templates blacklist to the templates filter if config.blacklist then local blacklist = table.concat( config.blacklist, ',' ) if params.templates then if string.sub( params.templates, 1, 1 ) == '-' then params.templates = params.templates .. ',' .. blacklist end else params.templates = '-' .. blacklist end end excerpt = Excerpt.filterTemplates( excerpt, params.templates ) -- Leave only the requested elements if params.only == 'file' or params.only == 'files' then local files = parser.getFiles( excerpt ) excerpt = params.only == 'file' and files[1] or table.concat( files, '\n\n' ) end if params.only == 'list' or params.only == 'lists' then local lists = parser.getLists( excerpt ) excerpt = params.only == 'list' and lists[1] or table.concat( lists, '\n\n' ) end if params.only == 'table' or params.only == 'tables' then local tables = parser.getTables( excerpt ) excerpt = params.only == 'table' and tables[1] or table.concat( tables, '\n\n' ) end if params.only == 'paragraph' or params.only == 'paragraphs' then local paragraphs = parser.getParagraphs( excerpt ) excerpt = params.only == 'paragraph' and paragraphs[1] or table.concat( paragraphs, '\n\n' ) end if params.only == 'template' or params.only == 'templates' then local templates = parser.getTemplates( excerpt ) excerpt = params.only == 'template' and templates[1] or table.concat( templates, '\n\n' ) end -- @todo Make more robust and move downwards if params.briefDates then excerpt = Excerpt.fixDates( excerpt ) end -- Remove unwanted elements excerpt = Excerpt.removeComments( excerpt ) excerpt = Excerpt.removeSelfLinks( excerpt ) excerpt = Excerpt.removeNonFreeFiles( excerpt ) excerpt = Excerpt.removeBehaviorSwitches( excerpt ) -- Fix or remove the references if params.references then excerpt = Excerpt.fixReferences( excerpt, page, wikitext ) else excerpt = Excerpt.removeReferences( excerpt ) end -- Remove wikilinks if not params.links then excerpt = Excerpt.removeLinks( excerpt ) end -- Link the bold text near the start of most leads and then remove it if not section then excerpt = Excerpt.linkBold( excerpt, page ) end if not params.bold then excerpt = Excerpt.removeBold( excerpt ) end -- Remove extra line breaks but leave one before and after so the parser interprets lists, tables, etc. correctly excerpt = excerpt:gsub( '\n\n\n+', '\n\n' ) excerpt = mw.text.trim( excerpt ) excerpt = '\n' .. excerpt .. '\n' -- Remove nested categories excerpt = frame:preprocess( excerpt ) excerpt = Excerpt.removeCategories( excerpt ) -- Add tracking categories if params.trackingCategories and config.categories then excerpt = Excerpt.addTrackingCategories( excerpt ) end -- Build the final output if params.inline then return mw.text.trim( excerpt ) end local tag = params.quote and 'blockquote' or 'div' local block = mw.html.create( tag ):addClass( 'excerpt-block' ):addClass( params.class ) if config.styles then local styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) block:node( styles ) end if params.hat then local hat = Excerpt.getHat( page, section, params ) block:node( hat ) end excerpt = mw.html.create( 'div' ):addClass( 'excerpt' ):wikitext( excerpt ) block:node( excerpt ) if params.more then local more = Excerpt.getReadMore( page, section ) block:node( more ) end return block end -- Filter the files in the given wikitext against the given filter function Excerpt.filterFiles( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local files = parser.getFiles( wikitext ) for index, file in pairs( files ) do local name = parser.getFileName( file ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( name, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( name, filters ) ) then wikitext = Excerpt.removeString( wikitext, file ) end end return wikitext end -- Filter the lists in the given wikitext against the given filter function Excerpt.filterLists( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local lists = parser.getLists( wikitext ) for index, list in pairs( lists ) do if isBlacklist and Excerpt.matchFilter( index, filters ) or not isBlacklist and not Excerpt.matchFilter( index, filters ) then wikitext = Excerpt.removeString( wikitext, list ) end end return wikitext end -- Filter the tables in the given wikitext against the given filter function Excerpt.filterTables( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local tables = parser.getTables( wikitext ) for index, tableWikitext in pairs( tables ) do local id = parser.getTableAttribute( tableWikitext, 'id' ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( id, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( id, filters ) ) then wikitext = Excerpt.removeString( wikitext, tableWikitext ) end end return wikitext end -- Filter the paragraphs in the given wikitext against the given filter function Excerpt.filterParagraphs( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local paragraphs = parser.getParagraphs( wikitext ) for index, paragraph in pairs( paragraphs ) do if isBlacklist and Excerpt.matchFilter( index, filters ) or not isBlacklist and not Excerpt.matchFilter( index, filters ) then wikitext = Excerpt.removeString( wikitext, paragraph ) end end return wikitext end -- Filter the templates in the given wikitext against the given filter function Excerpt.filterTemplates( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local templates = parser.getTemplates( wikitext ) for index, template in pairs( templates ) do local name = parser.getTemplateName( template ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( name, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( name, filters ) ) then wikitext = Excerpt.removeString( wikitext, template ) end end return wikitext end function Excerpt.addInfoboxFile( excerpt ) -- We cannot distinguish the infobox from the other templates, so we search them all local templates = parser.getTemplates( excerpt ) for _, template in pairs( templates ) do local parameters = parser.getTemplateParameters( template ) local file, captions, caption, cssClasses, cssClass for _, pair in pairs( config.captions ) do file = pair[1] file = parameters[file] if file and Excerpt.matchAny( file, '^.*%.', { '[Jj][Pp][Ee]?[Gg]', '[Pp][Nn][Gg]', '[Gg][Ii][Ff]', '[Ss][Vv][Gg]' }, '.*' ) then file = string.match( file, '%[?%[?.-:([^{|]+)%]?%]?' ) or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs( captions ) do if parameters[ p ] then caption = parameters[ p ] break end end -- Check for CSS classes -- We opt to use skin-invert-image instead of skin-invert -- in all other cases, the CSS provided in the infobox is used if pair[3] then cssClasses = pair[3] for _, p in pairs( cssClasses ) do if parameters[ p ] then cssClass = ( parameters[ p ] == 'skin-invert' ) and 'skin-invert-image' or parameters[ p ] break end end end local class = cssClass and ( '|class=' .. cssClass ) or '' return '[[File:' .. file .. class .. '|thumb|' .. ( caption or '' ) .. ']]' .. excerpt end end end return excerpt end function Excerpt.removeNonFreeFiles( wikitext ) local files = parser.getFiles( wikitext ) for _, file in pairs( files ) do local fileName = 'File:' .. parser.getFileName( file ) local fileTitle = mw.title.new( fileName ) if fileTitle then local fileDescription = fileTitle:getContent() if not fileDescription or fileDescription == '' then local frame = mw.getCurrentFrame() fileDescription = frame:preprocess( '{{' .. fileName .. '}}' ) -- try Commons end if fileDescription and string.match( fileDescription, '[Nn]on%-free' ) then wikitext = Excerpt.removeString( wikitext, file ) end end end return wikitext end function Excerpt.getHat( page, section, params ) local hat -- Build the text if params.this then hat = params.this elseif params.quote then hat = Excerpt.getMessage( 'this' ) elseif params.only then hat = Excerpt.getMessage( params.only ) else hat = Excerpt.getMessage( 'section' ) end hat = hat .. ' ' .. Excerpt.getMessage( 'excerpt' ) -- Build the link if section then hat = hat .. ' [[:' .. page .. '#' .. mw.uri.anchorEncode( section ) .. '|' .. params.displayTitle .. ' § ' .. section:gsub( '%[%[([^]|]+)|?[^]]*%]%]', '%1' ) .. ']].' -- remove nested links else hat = hat .. ' [[:' .. page .. '|' .. params.displayTitle .. ']].' end -- Build the edit link local title = mw.title.new( page ) local editUrl = title:fullUrl( 'action=edit' ) hat = hat .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. editUrl .. ' ' .. mw.message.new( 'editsection' ):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' if config.hat then local frame = mw.getCurrentFrame() hat = config.hat .. hat .. '}}' hat = frame:preprocess( hat ) else hat = mw.html.create( 'div' ):addClass( 'dablink excerpt-hat' ):wikitext( hat ) end return hat end function Excerpt.getReadMore( page, section ) local link = "'''[[" .. page if section then link = link .. '#' .. section end local text = Excerpt.getMessage( 'more' ) link = link .. '|' .. text .. "]]'''" link = mw.html.create( 'div' ):addClass( 'noprint excerpt-more' ):wikitext( link ) return link end -- Fix birth and death dates, but only in the first paragraph -- @todo Use parser.getParagraphs() to get the first paragraph function Excerpt.fixDates( excerpt ) local start = 1 -- skip initial templates local s local e = 0 repeat start = e + 1 s, e = mw.ustring.find( excerpt, '%s*%b{}%s*', start ) until not s or s > start s, e = mw.ustring.find( excerpt, '%b()', start ) -- get (...), which may be (year–year) if s and s < start + 100 then -- look only near the start local excerptStart = mw.ustring.sub( excerpt, s, e ) local year1, conjunction, year2 = string.match( excerptStart, '(%d%d%d+)(.-)(%d%d%d+)' ) if year1 and year2 and ( string.match( conjunction, '[%-–—]' ) or string.match( conjunction, '{{%s*[sS]nd%s*}}' ) ) then local y1 = tonumber( year1 ) local y2 = tonumber( year2 ) if y2 > y1 and y2 < y1 + 125 and y1 <= tonumber( os.date( '%Y' ) ) then excerpt = mw.ustring.sub( excerpt, 1, s ) .. year1 .. '–' .. year2 .. mw.ustring.sub( excerpt, e ) end end end return excerpt end -- Replace the first call to each reference defined outside of the excerpt for the full reference, to prevent undefined references -- Then prefix the page title to the reference names to prevent conflicts -- that is, replace <ref name="Foo"> for <ref name="Title of the article Foo"> -- and also <ref name="Foo" /> for <ref name="Title of the article Foo" /> -- also remove reference groups: <ref name="Foo" group="Bar"> for <ref name="Title of the article Foo"> -- and <ref group="Bar"> for <ref> -- @todo The current regex may fail in cases with both kinds of quotes, like <ref name="Darwin's book"> function Excerpt.fixReferences( excerpt, page, wikitext ) local references = parser.getReferences( excerpt ) local fixed = {} for _, reference in pairs( references ) do local name = parser.getTagAttribute( reference, 'name' ) if not fixed[ name ] then -- fix each reference only once local content = parser.getTagContent( reference ) if not content then -- reference is self-closing local full = parser.getReference( excerpt, name ) if not full then -- the reference is not defined in the excerpt full = parser.getReference( wikitext, name ) if full then excerpt = excerpt:gsub( Excerpt.escapeString( reference ), Excerpt.escapeString( full ), 1 ) end table.insert( fixed, name ) end end end end -- Prepend the page title to the reference names to prevent conflicts with other references in the transcluding page excerpt = excerpt:gsub( '< *[Rr][Ee][Ff][^>]*name *= *["\']?([^"\'>/]+)["\']?[^>/]*(/?) *>', '<ref name="' .. page:gsub( '"', '' ) .. ' %1"%2>' ) -- Remove reference groups because they don't apply to the transcluding page excerpt = excerpt:gsub( '< *[Rr][Ee][Ff] *group *= *["\']?[^"\'>/]+["\'] *>', '<ref>' ) return excerpt end function Excerpt.removeReferences( excerpt ) local references = parser.getReferences( excerpt ) for _, reference in pairs( references ) do excerpt = Excerpt.removeString( excerpt, reference ) end return excerpt end function Excerpt.removeCategories( excerpt ) local categories = parser.getCategories( excerpt ) for _, category in pairs( categories ) do excerpt = Excerpt.removeString( excerpt, category ) end return excerpt end function Excerpt.removeBehaviorSwitches( excerpt ) return excerpt:gsub( '__[A-Z]+__', '' ) end function Excerpt.removeComments( excerpt ) return excerpt:gsub( '<!%-%-.-%-%->', '' ) end function Excerpt.removeBold( excerpt ) return excerpt:gsub( "'''", '' ) end function Excerpt.removeLinks( excerpt ) local links = parser.getLinks( excerpt ) for _, link in pairs( links ) do excerpt = Excerpt.removeString( excerpt, link ) end return excerpt end -- @todo Use parser.getLinks function Excerpt.removeSelfLinks( excerpt ) local lang = mw.language.getContentLanguage() local page = Excerpt.escapeString( mw.title.getCurrentTitle().prefixedText ) local ucpage = lang:ucfirst( page ) local lcpage = lang:lcfirst( page ) excerpt = excerpt :gsub( '%[%[(' .. ucpage .. ')%]%]', '%1' ) :gsub( '%[%[(' .. lcpage .. ')%]%]', '%1' ) :gsub( '%[%[' .. ucpage .. '|([^]]+)%]%]', '%1' ) :gsub( '%[%[' .. lcpage .. '|([^]]+)%]%]', '%1' ) return excerpt end -- Replace the bold title or synonym near the start of the page by a link to the page function Excerpt.linkBold( excerpt, page ) local lang = mw.language.getContentLanguage() local position = mw.ustring.find( excerpt, "'''" .. lang:ucfirst( page ) .. "'''", 1, true ) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find( excerpt, "'''" .. lang:lcfirst( page ) .. "'''", 1, true ) -- plain search: special characters in page represent themselves if position then local length = mw.ustring.len( page ) excerpt = mw.ustring.sub( excerpt, 1, position + 2 ) .. '[[' .. mw.ustring.sub( excerpt, position + 3, position + length + 2 ) .. ']]' .. mw.ustring.sub( excerpt, position + length + 3, -1 ) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) excerpt = mw.ustring.gsub( excerpt, "'''(.-'*)'''", function ( text ) if not string.find( text, '%[' ) and not string.find( text, '%{' ) then -- if not wikilinked or some weird template return "'''[[" .. page .. '|' .. text .. "]]'''" -- replace '''Foo''' by '''[[page|Foo]]''' else return nil -- instruct gsub to make no change end end, 1 ) -- terminates the anonymous replacement function passed to gsub end return excerpt end function Excerpt.addTrackingCategories( excerpt ) local currentTitle = mw.title.getCurrentTitle() local contentCategory = config.categories.content if contentCategory and currentTitle.isContentPage then excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ currentTitle.namespace ] if namespaceCategory then excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end return excerpt end -- Helper method to match from a list of regular expressions -- Like so: match pre..list[1]..post or pre..list[2]..post or ... function Excerpt.matchAny( text, pre, list, post, init ) local match = {} for i = 1, #list do match = { mw.ustring.match( text, pre .. list[ i ] .. post, init ) } if match[1] then return unpack( match ) end end return nil end -- Helper function to get arguments -- args from Lua calls have priority over parent args from template function Excerpt.getArg( key, default ) local frame = mw.getCurrentFrame() for k, value in pairs( frame:getParent().args ) do if k == key and mw.text.trim( value ) ~= '' then return value end end for k, value in pairs( frame.args ) do if k == key and mw.text.trim( value ) ~= '' then return value end end return default end -- Helper method to get an error message -- This method also categorizes the current page in one of the configured error categories function Excerpt.getError( key, value ) local message = Excerpt.getMessage( 'error-' .. key, value ) local markup = mw.html.create( 'div' ):addClass( 'error' ):wikitext( message ) if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then markup:node( '[[Category:' .. config.categories.errors .. ']]' ) end return markup end -- Helper method to get a localized message -- This method uses Module:TNT to get localized messages from https://commons.wikimedia.org/wiki/Data:I18n/Module:Excerpt.tab -- If Module:TNT is not available or the localized message does not exist, the key is returned instead function Excerpt.getMessage( key, value ) local ok, TNT = pcall( require, 'Module:TNT' ) if not ok then return key end local ok2, message = pcall( TNT.format, 'I18n/Module:Excerpt.tab', key, value ) if not ok2 then return key end return message end -- Helper method to escape a string for use in regexes function Excerpt.escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Helper method to remove a string from a text -- @param text Text from where to remove the string -- @param str String to remove -- @return The given text with the string removed function Excerpt.removeString( text, str ) local pattern = Excerpt.escapeString( str ) if #pattern > 9999 then -- strings longer than 10000 bytes can't be put into regexes pattern = Excerpt.escapeString( mw.ustring.sub( str, 1, 999 ) ) .. '.-' .. Excerpt.escapeString( mw.ustring.sub( str, -999 ) ) end return text:gsub( pattern, '' ) end -- Helper method to convert a comma-separated list of numbers or min-max ranges into a list of booleans -- @param filter Required. Comma-separated list of numbers or min-max ranges, for example '1,3-5' -- @return Map from integers to booleans, for example {1=true,2=false,3=true,4=true,5=true} -- @return Boolean indicating whether the filters should be treated as a blacklist or not -- @note Merging this into matchFilter is possible, but way too inefficient function Excerpt.parseFilter( filter ) local filters = {} local isBlacklist = false if string.sub( filter, 1, 1 ) == '-' then isBlacklist = true filter = string.sub( filter, 2 ) end local values = mw.text.split( filter, ',' ) -- split values: '1,3-5' to {'1','3-5'} for _, value in pairs( values ) do value = mw.text.trim( value ) local min, max = mw.ustring.match( value, '^(%d+)%s*[-–—]%s*(%d+)$' ) -- '3-5' to min=3 max=5 if not max then min, max = string.match( value, '^((%d+))$' ) end -- '1' to min=1 max=1 if max then for i = min, max do filters[ i ] = true end else filters[ value ] = true -- if we reach this point, the string had the form 'a,b,c' rather than '1,2,3' end end local filter = {cache = {}, terms = filters} return filter, isBlacklist end -- Helper function to see if a value matches any of the given filters function Excerpt.matchFilter( value, filter ) if value == nil then return false elseif type(value) == "number" then return filter.terms[value] else local cached = filter.cache[value] if cached ~= nil then return cached end local lang = mw.language.getContentLanguage() local lcvalue = lang:lcfirst(value) local ucvalue = lang:ucfirst(value) for term in pairs( filter.terms ) do if value == tostring(term) or type(term) == "string" and ( lcvalue == term or ucvalue == term or mw.ustring.match( value, term ) ) then filter.cache[value] = true return true end end filter.cache[value] = false end end return Excerpt bdetulq45l9x0xj3wirpobi2tsdyau5 797359 797358 2025-10-21T12:43:57Z en>Sophivorus 0 Rename "trackingCategories" param to "track" 797359 Scribunto text/plain -- Module:Excerpt implements the Excerpt template -- Documentation and master version: https://en.wikipedia.org/wiki/Module:Excerpt -- Authors: User:Sophivorus, User:Certes, User:Aidan9382 & others -- License: CC-BY-SA-3.0 local parser = require( 'Module:WikitextParser' ) local yesno = require( 'Module:Yesno' ) local ok, config = pcall( require, 'Module:Excerpt/config' ) if not ok then config = {} end local Excerpt = {} -- Main entry point for templates function Excerpt.main( frame ) -- Make sure the requested page exists and get the wikitext local page = Excerpt.getArg( 1 ) if not page or page == '{{{1}}}' then return Excerpt.getError( 'no-page' ) end local title = mw.title.new( page ) if not title then return Excerpt.getError( 'invalid-title', page ) end local fragment = title.fragment -- save for later if title.isRedirect then title = title.redirectTarget if fragment == "" then fragment = title.fragment -- page merge potential end end if not title.exists then return Excerpt.getError( 'page-not-found', page ) end page = title.prefixedText local wikitext = title:getContent() -- Get the template params and process them local params = { hat = yesno( Excerpt.getArg( 'hat', true ) ), this = Excerpt.getArg( 'this' ), only = Excerpt.getArg( 'only' ), files = Excerpt.getArg( 'files', Excerpt.getArg( 'file' ) ), lists = Excerpt.getArg( 'lists', Excerpt.getArg( 'list' ) ), tables = Excerpt.getArg( 'tables', Excerpt.getArg( 'table' ) ), templates = Excerpt.getArg( 'templates', Excerpt.getArg( 'template' ) ), paragraphs = Excerpt.getArg( 'paragraphs', Excerpt.getArg( 'paragraph' ) ), references = yesno( Excerpt.getArg( 'references', true ) ), subsections = yesno( Excerpt.getArg( 'subsections', false ) ), links = yesno( Excerpt.getArg( 'links', true ) ), bold = yesno( Excerpt.getArg( 'bold', false ) ), briefDates = yesno( Excerpt.getArg( 'briefdates', false ) ), inline = yesno( Excerpt.getArg( 'inline' ) ), quote = yesno( Excerpt.getArg( 'quote' ) ), more = yesno( Excerpt.getArg( 'more' ) ), class = Excerpt.getArg( 'class' ), track = yesno( Excerpt.getArg( 'track', true ) ), displayTitle = Excerpt.getArg( 'displaytitle', page ), } -- Make sure the requested section exists and get the excerpt local excerpt local section = Excerpt.getArg( 2, fragment ) section = mw.text.trim( section ) if section == '' then section = nil end if section then excerpt = parser.getSectionTag( wikitext, section ) if not excerpt then if params.subsections then excerpt = parser.getSection( wikitext, section ) else local sections = parser.getSections( wikitext ) excerpt = sections[ section ] end end if not excerpt then return Excerpt.getError( 'section-not-found', section ) end if excerpt == '' then return Excerpt.getError( 'section-empty', section ) end else excerpt = parser.getLead( wikitext ) if excerpt == '' then return Excerpt.getError( 'lead-empty' ) end end -- Remove noinclude bits excerpt = excerpt:gsub( '<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>', '' ) -- Filter various elements from the excerpt excerpt = Excerpt.filterFiles( excerpt, params.files ) excerpt = Excerpt.filterLists( excerpt, params.lists ) excerpt = Excerpt.filterTables( excerpt, params.tables ) excerpt = Excerpt.filterParagraphs( excerpt, params.paragraphs ) -- If no file is found, try to get one from the infobox if ( params.only == 'file' or params.only == 'files' or not params.only and ( not params.files or params.files ~= '0' ) ) -- caller asked for files and not section -- and we're in the lead section and config.captions -- and we have the config option required to try finding files in infoboxes and #parser.getFiles( excerpt ) == 0 -- and there're no files in the excerpt then excerpt = Excerpt.addInfoboxFile( excerpt ) end -- Filter the templates by appending the templates blacklist to the templates filter if config.blacklist then local blacklist = table.concat( config.blacklist, ',' ) if params.templates then if string.sub( params.templates, 1, 1 ) == '-' then params.templates = params.templates .. ',' .. blacklist end else params.templates = '-' .. blacklist end end excerpt = Excerpt.filterTemplates( excerpt, params.templates ) -- Leave only the requested elements if params.only == 'file' or params.only == 'files' then local files = parser.getFiles( excerpt ) excerpt = params.only == 'file' and files[1] or table.concat( files, '\n\n' ) end if params.only == 'list' or params.only == 'lists' then local lists = parser.getLists( excerpt ) excerpt = params.only == 'list' and lists[1] or table.concat( lists, '\n\n' ) end if params.only == 'table' or params.only == 'tables' then local tables = parser.getTables( excerpt ) excerpt = params.only == 'table' and tables[1] or table.concat( tables, '\n\n' ) end if params.only == 'paragraph' or params.only == 'paragraphs' then local paragraphs = parser.getParagraphs( excerpt ) excerpt = params.only == 'paragraph' and paragraphs[1] or table.concat( paragraphs, '\n\n' ) end if params.only == 'template' or params.only == 'templates' then local templates = parser.getTemplates( excerpt ) excerpt = params.only == 'template' and templates[1] or table.concat( templates, '\n\n' ) end -- @todo Make more robust and move downwards if params.briefDates then excerpt = Excerpt.fixDates( excerpt ) end -- Remove unwanted elements excerpt = Excerpt.removeComments( excerpt ) excerpt = Excerpt.removeSelfLinks( excerpt ) excerpt = Excerpt.removeNonFreeFiles( excerpt ) excerpt = Excerpt.removeBehaviorSwitches( excerpt ) -- Fix or remove the references if params.references then excerpt = Excerpt.fixReferences( excerpt, page, wikitext ) else excerpt = Excerpt.removeReferences( excerpt ) end -- Remove wikilinks if not params.links then excerpt = Excerpt.removeLinks( excerpt ) end -- Link the bold text near the start of most leads and then remove it if not section then excerpt = Excerpt.linkBold( excerpt, page ) end if not params.bold then excerpt = Excerpt.removeBold( excerpt ) end -- Remove extra line breaks but leave one before and after so the parser interprets lists, tables, etc. correctly excerpt = excerpt:gsub( '\n\n\n+', '\n\n' ) excerpt = mw.text.trim( excerpt ) excerpt = '\n' .. excerpt .. '\n' -- Remove nested categories excerpt = frame:preprocess( excerpt ) excerpt = Excerpt.removeCategories( excerpt ) -- Add tracking categories if params.track and config.categories then excerpt = Excerpt.addTrackingCategories( excerpt ) end -- Build the final output if params.inline then return mw.text.trim( excerpt ) end local tag = params.quote and 'blockquote' or 'div' local block = mw.html.create( tag ):addClass( 'excerpt-block' ):addClass( params.class ) if config.styles then local styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) block:node( styles ) end if params.hat then local hat = Excerpt.getHat( page, section, params ) block:node( hat ) end excerpt = mw.html.create( 'div' ):addClass( 'excerpt' ):wikitext( excerpt ) block:node( excerpt ) if params.more then local more = Excerpt.getReadMore( page, section ) block:node( more ) end return block end -- Filter the files in the given wikitext against the given filter function Excerpt.filterFiles( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local files = parser.getFiles( wikitext ) for index, file in pairs( files ) do local name = parser.getFileName( file ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( name, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( name, filters ) ) then wikitext = Excerpt.removeString( wikitext, file ) end end return wikitext end -- Filter the lists in the given wikitext against the given filter function Excerpt.filterLists( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local lists = parser.getLists( wikitext ) for index, list in pairs( lists ) do if isBlacklist and Excerpt.matchFilter( index, filters ) or not isBlacklist and not Excerpt.matchFilter( index, filters ) then wikitext = Excerpt.removeString( wikitext, list ) end end return wikitext end -- Filter the tables in the given wikitext against the given filter function Excerpt.filterTables( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local tables = parser.getTables( wikitext ) for index, tableWikitext in pairs( tables ) do local id = parser.getTableAttribute( tableWikitext, 'id' ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( id, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( id, filters ) ) then wikitext = Excerpt.removeString( wikitext, tableWikitext ) end end return wikitext end -- Filter the paragraphs in the given wikitext against the given filter function Excerpt.filterParagraphs( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local paragraphs = parser.getParagraphs( wikitext ) for index, paragraph in pairs( paragraphs ) do if isBlacklist and Excerpt.matchFilter( index, filters ) or not isBlacklist and not Excerpt.matchFilter( index, filters ) then wikitext = Excerpt.removeString( wikitext, paragraph ) end end return wikitext end -- Filter the templates in the given wikitext against the given filter function Excerpt.filterTemplates( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local templates = parser.getTemplates( wikitext ) for index, template in pairs( templates ) do local name = parser.getTemplateName( template ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( name, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( name, filters ) ) then wikitext = Excerpt.removeString( wikitext, template ) end end return wikitext end function Excerpt.addInfoboxFile( excerpt ) -- We cannot distinguish the infobox from the other templates, so we search them all local templates = parser.getTemplates( excerpt ) for _, template in pairs( templates ) do local parameters = parser.getTemplateParameters( template ) local file, captions, caption, cssClasses, cssClass for _, pair in pairs( config.captions ) do file = pair[1] file = parameters[file] if file and Excerpt.matchAny( file, '^.*%.', { '[Jj][Pp][Ee]?[Gg]', '[Pp][Nn][Gg]', '[Gg][Ii][Ff]', '[Ss][Vv][Gg]' }, '.*' ) then file = string.match( file, '%[?%[?.-:([^{|]+)%]?%]?' ) or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs( captions ) do if parameters[ p ] then caption = parameters[ p ] break end end -- Check for CSS classes -- We opt to use skin-invert-image instead of skin-invert -- in all other cases, the CSS provided in the infobox is used if pair[3] then cssClasses = pair[3] for _, p in pairs( cssClasses ) do if parameters[ p ] then cssClass = ( parameters[ p ] == 'skin-invert' ) and 'skin-invert-image' or parameters[ p ] break end end end local class = cssClass and ( '|class=' .. cssClass ) or '' return '[[File:' .. file .. class .. '|thumb|' .. ( caption or '' ) .. ']]' .. excerpt end end end return excerpt end function Excerpt.removeNonFreeFiles( wikitext ) local files = parser.getFiles( wikitext ) for _, file in pairs( files ) do local fileName = 'File:' .. parser.getFileName( file ) local fileTitle = mw.title.new( fileName ) if fileTitle then local fileDescription = fileTitle:getContent() if not fileDescription or fileDescription == '' then local frame = mw.getCurrentFrame() fileDescription = frame:preprocess( '{{' .. fileName .. '}}' ) -- try Commons end if fileDescription and string.match( fileDescription, '[Nn]on%-free' ) then wikitext = Excerpt.removeString( wikitext, file ) end end end return wikitext end function Excerpt.getHat( page, section, params ) local hat -- Build the text if params.this then hat = params.this elseif params.quote then hat = Excerpt.getMessage( 'this' ) elseif params.only then hat = Excerpt.getMessage( params.only ) else hat = Excerpt.getMessage( 'section' ) end hat = hat .. ' ' .. Excerpt.getMessage( 'excerpt' ) -- Build the link if section then hat = hat .. ' [[:' .. page .. '#' .. mw.uri.anchorEncode( section ) .. '|' .. params.displayTitle .. ' § ' .. section:gsub( '%[%[([^]|]+)|?[^]]*%]%]', '%1' ) .. ']].' -- remove nested links else hat = hat .. ' [[:' .. page .. '|' .. params.displayTitle .. ']].' end -- Build the edit link local title = mw.title.new( page ) local editUrl = title:fullUrl( 'action=edit' ) hat = hat .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. editUrl .. ' ' .. mw.message.new( 'editsection' ):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' if config.hat then local frame = mw.getCurrentFrame() hat = config.hat .. hat .. '}}' hat = frame:preprocess( hat ) else hat = mw.html.create( 'div' ):addClass( 'dablink excerpt-hat' ):wikitext( hat ) end return hat end function Excerpt.getReadMore( page, section ) local link = "'''[[" .. page if section then link = link .. '#' .. section end local text = Excerpt.getMessage( 'more' ) link = link .. '|' .. text .. "]]'''" link = mw.html.create( 'div' ):addClass( 'noprint excerpt-more' ):wikitext( link ) return link end -- Fix birth and death dates, but only in the first paragraph -- @todo Use parser.getParagraphs() to get the first paragraph function Excerpt.fixDates( excerpt ) local start = 1 -- skip initial templates local s local e = 0 repeat start = e + 1 s, e = mw.ustring.find( excerpt, '%s*%b{}%s*', start ) until not s or s > start s, e = mw.ustring.find( excerpt, '%b()', start ) -- get (...), which may be (year–year) if s and s < start + 100 then -- look only near the start local excerptStart = mw.ustring.sub( excerpt, s, e ) local year1, conjunction, year2 = string.match( excerptStart, '(%d%d%d+)(.-)(%d%d%d+)' ) if year1 and year2 and ( string.match( conjunction, '[%-–—]' ) or string.match( conjunction, '{{%s*[sS]nd%s*}}' ) ) then local y1 = tonumber( year1 ) local y2 = tonumber( year2 ) if y2 > y1 and y2 < y1 + 125 and y1 <= tonumber( os.date( '%Y' ) ) then excerpt = mw.ustring.sub( excerpt, 1, s ) .. year1 .. '–' .. year2 .. mw.ustring.sub( excerpt, e ) end end end return excerpt end -- Replace the first call to each reference defined outside of the excerpt for the full reference, to prevent undefined references -- Then prefix the page title to the reference names to prevent conflicts -- that is, replace <ref name="Foo"> for <ref name="Title of the article Foo"> -- and also <ref name="Foo" /> for <ref name="Title of the article Foo" /> -- also remove reference groups: <ref name="Foo" group="Bar"> for <ref name="Title of the article Foo"> -- and <ref group="Bar"> for <ref> -- @todo The current regex may fail in cases with both kinds of quotes, like <ref name="Darwin's book"> function Excerpt.fixReferences( excerpt, page, wikitext ) local references = parser.getReferences( excerpt ) local fixed = {} for _, reference in pairs( references ) do local name = parser.getTagAttribute( reference, 'name' ) if not fixed[ name ] then -- fix each reference only once local content = parser.getTagContent( reference ) if not content then -- reference is self-closing local full = parser.getReference( excerpt, name ) if not full then -- the reference is not defined in the excerpt full = parser.getReference( wikitext, name ) if full then excerpt = excerpt:gsub( Excerpt.escapeString( reference ), Excerpt.escapeString( full ), 1 ) end table.insert( fixed, name ) end end end end -- Prepend the page title to the reference names to prevent conflicts with other references in the transcluding page excerpt = excerpt:gsub( '< *[Rr][Ee][Ff][^>]*name *= *["\']?([^"\'>/]+)["\']?[^>/]*(/?) *>', '<ref name="' .. page:gsub( '"', '' ) .. ' %1"%2>' ) -- Remove reference groups because they don't apply to the transcluding page excerpt = excerpt:gsub( '< *[Rr][Ee][Ff] *group *= *["\']?[^"\'>/]+["\'] *>', '<ref>' ) return excerpt end function Excerpt.removeReferences( excerpt ) local references = parser.getReferences( excerpt ) for _, reference in pairs( references ) do excerpt = Excerpt.removeString( excerpt, reference ) end return excerpt end function Excerpt.removeCategories( excerpt ) local categories = parser.getCategories( excerpt ) for _, category in pairs( categories ) do excerpt = Excerpt.removeString( excerpt, category ) end return excerpt end function Excerpt.removeBehaviorSwitches( excerpt ) return excerpt:gsub( '__[A-Z]+__', '' ) end function Excerpt.removeComments( excerpt ) return excerpt:gsub( '<!%-%-.-%-%->', '' ) end function Excerpt.removeBold( excerpt ) return excerpt:gsub( "'''", '' ) end function Excerpt.removeLinks( excerpt ) local links = parser.getLinks( excerpt ) for _, link in pairs( links ) do excerpt = Excerpt.removeString( excerpt, link ) end return excerpt end -- @todo Use parser.getLinks function Excerpt.removeSelfLinks( excerpt ) local lang = mw.language.getContentLanguage() local page = Excerpt.escapeString( mw.title.getCurrentTitle().prefixedText ) local ucpage = lang:ucfirst( page ) local lcpage = lang:lcfirst( page ) excerpt = excerpt :gsub( '%[%[(' .. ucpage .. ')%]%]', '%1' ) :gsub( '%[%[(' .. lcpage .. ')%]%]', '%1' ) :gsub( '%[%[' .. ucpage .. '|([^]]+)%]%]', '%1' ) :gsub( '%[%[' .. lcpage .. '|([^]]+)%]%]', '%1' ) return excerpt end -- Replace the bold title or synonym near the start of the page by a link to the page function Excerpt.linkBold( excerpt, page ) local lang = mw.language.getContentLanguage() local position = mw.ustring.find( excerpt, "'''" .. lang:ucfirst( page ) .. "'''", 1, true ) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find( excerpt, "'''" .. lang:lcfirst( page ) .. "'''", 1, true ) -- plain search: special characters in page represent themselves if position then local length = mw.ustring.len( page ) excerpt = mw.ustring.sub( excerpt, 1, position + 2 ) .. '[[' .. mw.ustring.sub( excerpt, position + 3, position + length + 2 ) .. ']]' .. mw.ustring.sub( excerpt, position + length + 3, -1 ) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) excerpt = mw.ustring.gsub( excerpt, "'''(.-'*)'''", function ( text ) if not string.find( text, '%[' ) and not string.find( text, '%{' ) then -- if not wikilinked or some weird template return "'''[[" .. page .. '|' .. text .. "]]'''" -- replace '''Foo''' by '''[[page|Foo]]''' else return nil -- instruct gsub to make no change end end, 1 ) -- terminates the anonymous replacement function passed to gsub end return excerpt end function Excerpt.addTrackingCategories( excerpt ) local currentTitle = mw.title.getCurrentTitle() local contentCategory = config.categories.content if contentCategory and currentTitle.isContentPage then excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ currentTitle.namespace ] if namespaceCategory then excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end return excerpt end -- Helper method to match from a list of regular expressions -- Like so: match pre..list[1]..post or pre..list[2]..post or ... function Excerpt.matchAny( text, pre, list, post, init ) local match = {} for i = 1, #list do match = { mw.ustring.match( text, pre .. list[ i ] .. post, init ) } if match[1] then return unpack( match ) end end return nil end -- Helper function to get arguments -- args from Lua calls have priority over parent args from template function Excerpt.getArg( key, default ) local frame = mw.getCurrentFrame() for k, value in pairs( frame:getParent().args ) do if k == key and mw.text.trim( value ) ~= '' then return value end end for k, value in pairs( frame.args ) do if k == key and mw.text.trim( value ) ~= '' then return value end end return default end -- Helper method to get an error message -- This method also categorizes the current page in one of the configured error categories function Excerpt.getError( key, value ) local message = Excerpt.getMessage( 'error-' .. key, value ) local markup = mw.html.create( 'div' ):addClass( 'error' ):wikitext( message ) if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then markup:node( '[[Category:' .. config.categories.errors .. ']]' ) end return markup end -- Helper method to get a localized message -- This method uses Module:TNT to get localized messages from https://commons.wikimedia.org/wiki/Data:I18n/Module:Excerpt.tab -- If Module:TNT is not available or the localized message does not exist, the key is returned instead function Excerpt.getMessage( key, value ) local ok, TNT = pcall( require, 'Module:TNT' ) if not ok then return key end local ok2, message = pcall( TNT.format, 'I18n/Module:Excerpt.tab', key, value ) if not ok2 then return key end return message end -- Helper method to escape a string for use in regexes function Excerpt.escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Helper method to remove a string from a text -- @param text Text from where to remove the string -- @param str String to remove -- @return The given text with the string removed function Excerpt.removeString( text, str ) local pattern = Excerpt.escapeString( str ) if #pattern > 9999 then -- strings longer than 10000 bytes can't be put into regexes pattern = Excerpt.escapeString( mw.ustring.sub( str, 1, 999 ) ) .. '.-' .. Excerpt.escapeString( mw.ustring.sub( str, -999 ) ) end return text:gsub( pattern, '' ) end -- Helper method to convert a comma-separated list of numbers or min-max ranges into a list of booleans -- @param filter Required. Comma-separated list of numbers or min-max ranges, for example '1,3-5' -- @return Map from integers to booleans, for example {1=true,2=false,3=true,4=true,5=true} -- @return Boolean indicating whether the filters should be treated as a blacklist or not -- @note Merging this into matchFilter is possible, but way too inefficient function Excerpt.parseFilter( filter ) local filters = {} local isBlacklist = false if string.sub( filter, 1, 1 ) == '-' then isBlacklist = true filter = string.sub( filter, 2 ) end local values = mw.text.split( filter, ',' ) -- split values: '1,3-5' to {'1','3-5'} for _, value in pairs( values ) do value = mw.text.trim( value ) local min, max = mw.ustring.match( value, '^(%d+)%s*[-–—]%s*(%d+)$' ) -- '3-5' to min=3 max=5 if not max then min, max = string.match( value, '^((%d+))$' ) end -- '1' to min=1 max=1 if max then for i = min, max do filters[ i ] = true end else filters[ value ] = true -- if we reach this point, the string had the form 'a,b,c' rather than '1,2,3' end end local filter = {cache = {}, terms = filters} return filter, isBlacklist end -- Helper function to see if a value matches any of the given filters function Excerpt.matchFilter( value, filter ) if value == nil then return false elseif type(value) == "number" then return filter.terms[value] else local cached = filter.cache[value] if cached ~= nil then return cached end local lang = mw.language.getContentLanguage() local lcvalue = lang:lcfirst(value) local ucvalue = lang:ucfirst(value) for term in pairs( filter.terms ) do if value == tostring(term) or type(term) == "string" and ( lcvalue == term or ucvalue == term or mw.ustring.match( value, term ) ) then filter.cache[value] = true return true end end filter.cache[value] = false end end return Excerpt chuepw1c3inkl98a1e82d15szf6b0ll 797360 797359 2025-12-23T17:27:54Z en>Aidan9382 0 Ensure that the padding newline is retained when tracking categories are added 797360 Scribunto text/plain -- Module:Excerpt implements the Excerpt template -- Documentation and master version: https://en.wikipedia.org/wiki/Module:Excerpt -- Authors: User:Sophivorus, User:Certes, User:Aidan9382 & others -- License: CC-BY-SA-3.0 local parser = require( 'Module:WikitextParser' ) local yesno = require( 'Module:Yesno' ) local ok, config = pcall( require, 'Module:Excerpt/config' ) if not ok then config = {} end local Excerpt = {} -- Main entry point for templates function Excerpt.main( frame ) -- Make sure the requested page exists and get the wikitext local page = Excerpt.getArg( 1 ) if not page or page == '{{{1}}}' then return Excerpt.getError( 'no-page' ) end local title = mw.title.new( page ) if not title then return Excerpt.getError( 'invalid-title', page ) end local fragment = title.fragment -- save for later if title.isRedirect then title = title.redirectTarget if fragment == "" then fragment = title.fragment -- page merge potential end end if not title.exists then return Excerpt.getError( 'page-not-found', page ) end page = title.prefixedText local wikitext = title:getContent() -- Get the template params and process them local params = { hat = yesno( Excerpt.getArg( 'hat', true ) ), this = Excerpt.getArg( 'this' ), only = Excerpt.getArg( 'only' ), files = Excerpt.getArg( 'files', Excerpt.getArg( 'file' ) ), lists = Excerpt.getArg( 'lists', Excerpt.getArg( 'list' ) ), tables = Excerpt.getArg( 'tables', Excerpt.getArg( 'table' ) ), templates = Excerpt.getArg( 'templates', Excerpt.getArg( 'template' ) ), paragraphs = Excerpt.getArg( 'paragraphs', Excerpt.getArg( 'paragraph' ) ), references = yesno( Excerpt.getArg( 'references', true ) ), subsections = yesno( Excerpt.getArg( 'subsections', false ) ), links = yesno( Excerpt.getArg( 'links', true ) ), bold = yesno( Excerpt.getArg( 'bold', false ) ), briefDates = yesno( Excerpt.getArg( 'briefdates', false ) ), inline = yesno( Excerpt.getArg( 'inline' ) ), quote = yesno( Excerpt.getArg( 'quote' ) ), more = yesno( Excerpt.getArg( 'more' ) ), class = Excerpt.getArg( 'class' ), track = yesno( Excerpt.getArg( 'track', true ) ), displayTitle = Excerpt.getArg( 'displaytitle', page ), } -- Make sure the requested section exists and get the excerpt local excerpt local section = Excerpt.getArg( 2, fragment ) section = mw.text.trim( section ) if section == '' then section = nil end if section then excerpt = parser.getSectionTag( wikitext, section ) if not excerpt then if params.subsections then excerpt = parser.getSection( wikitext, section ) else local sections = parser.getSections( wikitext ) excerpt = sections[ section ] end end if not excerpt then return Excerpt.getError( 'section-not-found', section ) end if excerpt == '' then return Excerpt.getError( 'section-empty', section ) end else excerpt = parser.getLead( wikitext ) if excerpt == '' then return Excerpt.getError( 'lead-empty' ) end end -- Remove noinclude bits excerpt = excerpt:gsub( '<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>', '' ) -- Filter various elements from the excerpt excerpt = Excerpt.filterFiles( excerpt, params.files ) excerpt = Excerpt.filterLists( excerpt, params.lists ) excerpt = Excerpt.filterTables( excerpt, params.tables ) excerpt = Excerpt.filterParagraphs( excerpt, params.paragraphs ) -- If no file is found, try to get one from the infobox if ( params.only == 'file' or params.only == 'files' or not params.only and ( not params.files or params.files ~= '0' ) ) -- caller asked for files and not section -- and we're in the lead section and config.captions -- and we have the config option required to try finding files in infoboxes and #parser.getFiles( excerpt ) == 0 -- and there're no files in the excerpt then excerpt = Excerpt.addInfoboxFile( excerpt ) end -- Filter the templates by appending the templates blacklist to the templates filter if config.blacklist then local blacklist = table.concat( config.blacklist, ',' ) if params.templates then if string.sub( params.templates, 1, 1 ) == '-' then params.templates = params.templates .. ',' .. blacklist end else params.templates = '-' .. blacklist end end excerpt = Excerpt.filterTemplates( excerpt, params.templates ) -- Leave only the requested elements if params.only == 'file' or params.only == 'files' then local files = parser.getFiles( excerpt ) excerpt = params.only == 'file' and files[1] or table.concat( files, '\n\n' ) end if params.only == 'list' or params.only == 'lists' then local lists = parser.getLists( excerpt ) excerpt = params.only == 'list' and lists[1] or table.concat( lists, '\n\n' ) end if params.only == 'table' or params.only == 'tables' then local tables = parser.getTables( excerpt ) excerpt = params.only == 'table' and tables[1] or table.concat( tables, '\n\n' ) end if params.only == 'paragraph' or params.only == 'paragraphs' then local paragraphs = parser.getParagraphs( excerpt ) excerpt = params.only == 'paragraph' and paragraphs[1] or table.concat( paragraphs, '\n\n' ) end if params.only == 'template' or params.only == 'templates' then local templates = parser.getTemplates( excerpt ) excerpt = params.only == 'template' and templates[1] or table.concat( templates, '\n\n' ) end -- @todo Make more robust and move downwards if params.briefDates then excerpt = Excerpt.fixDates( excerpt ) end -- Remove unwanted elements excerpt = Excerpt.removeComments( excerpt ) excerpt = Excerpt.removeSelfLinks( excerpt ) excerpt = Excerpt.removeNonFreeFiles( excerpt ) excerpt = Excerpt.removeBehaviorSwitches( excerpt ) -- Fix or remove the references if params.references then excerpt = Excerpt.fixReferences( excerpt, page, wikitext ) else excerpt = Excerpt.removeReferences( excerpt ) end -- Remove wikilinks if not params.links then excerpt = Excerpt.removeLinks( excerpt ) end -- Link the bold text near the start of most leads and then remove it if not section then excerpt = Excerpt.linkBold( excerpt, page ) end if not params.bold then excerpt = Excerpt.removeBold( excerpt ) end -- Remove extra line breaks but leave one before and after so the parser interprets lists, tables, etc. correctly excerpt = excerpt:gsub( '\n\n\n+', '\n\n' ) excerpt = mw.text.trim( excerpt ) excerpt = '\n' .. excerpt .. '\n' -- Remove nested categories excerpt = frame:preprocess( excerpt ) excerpt = Excerpt.removeCategories( excerpt ) -- Add tracking categories if params.track and config.categories then excerpt = Excerpt.addTrackingCategories( excerpt ) end -- Build the final output if params.inline then return mw.text.trim( excerpt ) end local tag = params.quote and 'blockquote' or 'div' local block = mw.html.create( tag ):addClass( 'excerpt-block' ):addClass( params.class ) if config.styles then local styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) block:node( styles ) end if params.hat then local hat = Excerpt.getHat( page, section, params ) block:node( hat ) end excerpt = mw.html.create( 'div' ):addClass( 'excerpt' ):wikitext( excerpt ) block:node( excerpt ) if params.more then local more = Excerpt.getReadMore( page, section ) block:node( more ) end return block end -- Filter the files in the given wikitext against the given filter function Excerpt.filterFiles( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local files = parser.getFiles( wikitext ) for index, file in pairs( files ) do local name = parser.getFileName( file ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( name, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( name, filters ) ) then wikitext = Excerpt.removeString( wikitext, file ) end end return wikitext end -- Filter the lists in the given wikitext against the given filter function Excerpt.filterLists( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local lists = parser.getLists( wikitext ) for index, list in pairs( lists ) do if isBlacklist and Excerpt.matchFilter( index, filters ) or not isBlacklist and not Excerpt.matchFilter( index, filters ) then wikitext = Excerpt.removeString( wikitext, list ) end end return wikitext end -- Filter the tables in the given wikitext against the given filter function Excerpt.filterTables( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local tables = parser.getTables( wikitext ) for index, tableWikitext in pairs( tables ) do local id = parser.getTableAttribute( tableWikitext, 'id' ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( id, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( id, filters ) ) then wikitext = Excerpt.removeString( wikitext, tableWikitext ) end end return wikitext end -- Filter the paragraphs in the given wikitext against the given filter function Excerpt.filterParagraphs( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local paragraphs = parser.getParagraphs( wikitext ) for index, paragraph in pairs( paragraphs ) do if isBlacklist and Excerpt.matchFilter( index, filters ) or not isBlacklist and not Excerpt.matchFilter( index, filters ) then wikitext = Excerpt.removeString( wikitext, paragraph ) end end return wikitext end -- Filter the templates in the given wikitext against the given filter function Excerpt.filterTemplates( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local templates = parser.getTemplates( wikitext ) for index, template in pairs( templates ) do local name = parser.getTemplateName( template ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( name, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( name, filters ) ) then wikitext = Excerpt.removeString( wikitext, template ) end end return wikitext end function Excerpt.addInfoboxFile( excerpt ) -- We cannot distinguish the infobox from the other templates, so we search them all local templates = parser.getTemplates( excerpt ) for _, template in pairs( templates ) do local parameters = parser.getTemplateParameters( template ) local file, captions, caption, cssClasses, cssClass for _, pair in pairs( config.captions ) do file = pair[1] file = parameters[file] if file and Excerpt.matchAny( file, '^.*%.', { '[Jj][Pp][Ee]?[Gg]', '[Pp][Nn][Gg]', '[Gg][Ii][Ff]', '[Ss][Vv][Gg]' }, '.*' ) then file = string.match( file, '%[?%[?.-:([^{|]+)%]?%]?' ) or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs( captions ) do if parameters[ p ] then caption = parameters[ p ] break end end -- Check for CSS classes -- We opt to use skin-invert-image instead of skin-invert -- in all other cases, the CSS provided in the infobox is used if pair[3] then cssClasses = pair[3] for _, p in pairs( cssClasses ) do if parameters[ p ] then cssClass = ( parameters[ p ] == 'skin-invert' ) and 'skin-invert-image' or parameters[ p ] break end end end local class = cssClass and ( '|class=' .. cssClass ) or '' return '[[File:' .. file .. class .. '|thumb|' .. ( caption or '' ) .. ']]' .. excerpt end end end return excerpt end function Excerpt.removeNonFreeFiles( wikitext ) local files = parser.getFiles( wikitext ) for _, file in pairs( files ) do local fileName = 'File:' .. parser.getFileName( file ) local fileTitle = mw.title.new( fileName ) if fileTitle then local fileDescription = fileTitle:getContent() if not fileDescription or fileDescription == '' then local frame = mw.getCurrentFrame() fileDescription = frame:preprocess( '{{' .. fileName .. '}}' ) -- try Commons end if fileDescription and string.match( fileDescription, '[Nn]on%-free' ) then wikitext = Excerpt.removeString( wikitext, file ) end end end return wikitext end function Excerpt.getHat( page, section, params ) local hat -- Build the text if params.this then hat = params.this elseif params.quote then hat = Excerpt.getMessage( 'this' ) elseif params.only then hat = Excerpt.getMessage( params.only ) else hat = Excerpt.getMessage( 'section' ) end hat = hat .. ' ' .. Excerpt.getMessage( 'excerpt' ) -- Build the link if section then hat = hat .. ' [[:' .. page .. '#' .. mw.uri.anchorEncode( section ) .. '|' .. params.displayTitle .. ' § ' .. section:gsub( '%[%[([^]|]+)|?[^]]*%]%]', '%1' ) .. ']].' -- remove nested links else hat = hat .. ' [[:' .. page .. '|' .. params.displayTitle .. ']].' end -- Build the edit link local title = mw.title.new( page ) local editUrl = title:fullUrl( 'action=edit' ) hat = hat .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. editUrl .. ' ' .. mw.message.new( 'editsection' ):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' if config.hat then local frame = mw.getCurrentFrame() hat = config.hat .. hat .. '}}' hat = frame:preprocess( hat ) else hat = mw.html.create( 'div' ):addClass( 'dablink excerpt-hat' ):wikitext( hat ) end return hat end function Excerpt.getReadMore( page, section ) local link = "'''[[" .. page if section then link = link .. '#' .. section end local text = Excerpt.getMessage( 'more' ) link = link .. '|' .. text .. "]]'''" link = mw.html.create( 'div' ):addClass( 'noprint excerpt-more' ):wikitext( link ) return link end -- Fix birth and death dates, but only in the first paragraph -- @todo Use parser.getParagraphs() to get the first paragraph function Excerpt.fixDates( excerpt ) local start = 1 -- skip initial templates local s local e = 0 repeat start = e + 1 s, e = mw.ustring.find( excerpt, '%s*%b{}%s*', start ) until not s or s > start s, e = mw.ustring.find( excerpt, '%b()', start ) -- get (...), which may be (year–year) if s and s < start + 100 then -- look only near the start local excerptStart = mw.ustring.sub( excerpt, s, e ) local year1, conjunction, year2 = string.match( excerptStart, '(%d%d%d+)(.-)(%d%d%d+)' ) if year1 and year2 and ( string.match( conjunction, '[%-–—]' ) or string.match( conjunction, '{{%s*[sS]nd%s*}}' ) ) then local y1 = tonumber( year1 ) local y2 = tonumber( year2 ) if y2 > y1 and y2 < y1 + 125 and y1 <= tonumber( os.date( '%Y' ) ) then excerpt = mw.ustring.sub( excerpt, 1, s ) .. year1 .. '–' .. year2 .. mw.ustring.sub( excerpt, e ) end end end return excerpt end -- Replace the first call to each reference defined outside of the excerpt for the full reference, to prevent undefined references -- Then prefix the page title to the reference names to prevent conflicts -- that is, replace <ref name="Foo"> for <ref name="Title of the article Foo"> -- and also <ref name="Foo" /> for <ref name="Title of the article Foo" /> -- also remove reference groups: <ref name="Foo" group="Bar"> for <ref name="Title of the article Foo"> -- and <ref group="Bar"> for <ref> -- @todo The current regex may fail in cases with both kinds of quotes, like <ref name="Darwin's book"> function Excerpt.fixReferences( excerpt, page, wikitext ) local references = parser.getReferences( excerpt ) local fixed = {} for _, reference in pairs( references ) do local name = parser.getTagAttribute( reference, 'name' ) if not fixed[ name ] then -- fix each reference only once local content = parser.getTagContent( reference ) if not content then -- reference is self-closing local full = parser.getReference( excerpt, name ) if not full then -- the reference is not defined in the excerpt full = parser.getReference( wikitext, name ) if full then excerpt = excerpt:gsub( Excerpt.escapeString( reference ), Excerpt.escapeString( full ), 1 ) end table.insert( fixed, name ) end end end end -- Prepend the page title to the reference names to prevent conflicts with other references in the transcluding page excerpt = excerpt:gsub( '< *[Rr][Ee][Ff][^>]*name *= *["\']?([^"\'>/]+)["\']?[^>/]*(/?) *>', '<ref name="' .. page:gsub( '"', '' ) .. ' %1"%2>' ) -- Remove reference groups because they don't apply to the transcluding page excerpt = excerpt:gsub( '< *[Rr][Ee][Ff] *group *= *["\']?[^"\'>/]+["\'] *>', '<ref>' ) return excerpt end function Excerpt.removeReferences( excerpt ) local references = parser.getReferences( excerpt ) for _, reference in pairs( references ) do excerpt = Excerpt.removeString( excerpt, reference ) end return excerpt end function Excerpt.removeCategories( excerpt ) local categories = parser.getCategories( excerpt ) for _, category in pairs( categories ) do excerpt = Excerpt.removeString( excerpt, category ) end return excerpt end function Excerpt.removeBehaviorSwitches( excerpt ) return excerpt:gsub( '__[A-Z]+__', '' ) end function Excerpt.removeComments( excerpt ) return excerpt:gsub( '<!%-%-.-%-%->', '' ) end function Excerpt.removeBold( excerpt ) return excerpt:gsub( "'''", '' ) end function Excerpt.removeLinks( excerpt ) local links = parser.getLinks( excerpt ) for _, link in pairs( links ) do excerpt = Excerpt.removeString( excerpt, link ) end return excerpt end -- @todo Use parser.getLinks function Excerpt.removeSelfLinks( excerpt ) local lang = mw.language.getContentLanguage() local page = Excerpt.escapeString( mw.title.getCurrentTitle().prefixedText ) local ucpage = lang:ucfirst( page ) local lcpage = lang:lcfirst( page ) excerpt = excerpt :gsub( '%[%[(' .. ucpage .. ')%]%]', '%1' ) :gsub( '%[%[(' .. lcpage .. ')%]%]', '%1' ) :gsub( '%[%[' .. ucpage .. '|([^]]+)%]%]', '%1' ) :gsub( '%[%[' .. lcpage .. '|([^]]+)%]%]', '%1' ) return excerpt end -- Replace the bold title or synonym near the start of the page by a link to the page function Excerpt.linkBold( excerpt, page ) local lang = mw.language.getContentLanguage() local position = mw.ustring.find( excerpt, "'''" .. lang:ucfirst( page ) .. "'''", 1, true ) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find( excerpt, "'''" .. lang:lcfirst( page ) .. "'''", 1, true ) -- plain search: special characters in page represent themselves if position then local length = mw.ustring.len( page ) excerpt = mw.ustring.sub( excerpt, 1, position + 2 ) .. '[[' .. mw.ustring.sub( excerpt, position + 3, position + length + 2 ) .. ']]' .. mw.ustring.sub( excerpt, position + length + 3, -1 ) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) excerpt = mw.ustring.gsub( excerpt, "'''(.-'*)'''", function ( text ) if not string.find( text, '%[' ) and not string.find( text, '%{' ) then -- if not wikilinked or some weird template return "'''[[" .. page .. '|' .. text .. "]]'''" -- replace '''Foo''' by '''[[page|Foo]]''' else return nil -- instruct gsub to make no change end end, 1 ) -- terminates the anonymous replacement function passed to gsub end return excerpt end function Excerpt.addTrackingCategories( excerpt ) local currentTitle = mw.title.getCurrentTitle() local addedCategories = false local contentCategory = config.categories.content if contentCategory and currentTitle.isContentPage then addedCategories = true excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ currentTitle.namespace ] if namespaceCategory then addedCategories = true excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end if addedCategories then excerpt = excerpt .. '\n' end return excerpt end -- Helper method to match from a list of regular expressions -- Like so: match pre..list[1]..post or pre..list[2]..post or ... function Excerpt.matchAny( text, pre, list, post, init ) local match = {} for i = 1, #list do match = { mw.ustring.match( text, pre .. list[ i ] .. post, init ) } if match[1] then return unpack( match ) end end return nil end -- Helper function to get arguments -- args from Lua calls have priority over parent args from template function Excerpt.getArg( key, default ) local frame = mw.getCurrentFrame() for k, value in pairs( frame:getParent().args ) do if k == key and mw.text.trim( value ) ~= '' then return value end end for k, value in pairs( frame.args ) do if k == key and mw.text.trim( value ) ~= '' then return value end end return default end -- Helper method to get an error message -- This method also categorizes the current page in one of the configured error categories function Excerpt.getError( key, value ) local message = Excerpt.getMessage( 'error-' .. key, value ) local markup = mw.html.create( 'div' ):addClass( 'error' ):wikitext( message ) if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then markup:node( '[[Category:' .. config.categories.errors .. ']]' ) end return markup end -- Helper method to get a localized message -- This method uses Module:TNT to get localized messages from https://commons.wikimedia.org/wiki/Data:I18n/Module:Excerpt.tab -- If Module:TNT is not available or the localized message does not exist, the key is returned instead function Excerpt.getMessage( key, value ) local ok, TNT = pcall( require, 'Module:TNT' ) if not ok then return key end local ok2, message = pcall( TNT.format, 'I18n/Module:Excerpt.tab', key, value ) if not ok2 then return key end return message end -- Helper method to escape a string for use in regexes function Excerpt.escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Helper method to remove a string from a text -- @param text Text from where to remove the string -- @param str String to remove -- @return The given text with the string removed function Excerpt.removeString( text, str ) local pattern = Excerpt.escapeString( str ) if #pattern > 9999 then -- strings longer than 10000 bytes can't be put into regexes pattern = Excerpt.escapeString( mw.ustring.sub( str, 1, 999 ) ) .. '.-' .. Excerpt.escapeString( mw.ustring.sub( str, -999 ) ) end return text:gsub( pattern, '' ) end -- Helper method to convert a comma-separated list of numbers or min-max ranges into a list of booleans -- @param filter Required. Comma-separated list of numbers or min-max ranges, for example '1,3-5' -- @return Map from integers to booleans, for example {1=true,2=false,3=true,4=true,5=true} -- @return Boolean indicating whether the filters should be treated as a blacklist or not -- @note Merging this into matchFilter is possible, but way too inefficient function Excerpt.parseFilter( filter ) local filters = {} local isBlacklist = false if string.sub( filter, 1, 1 ) == '-' then isBlacklist = true filter = string.sub( filter, 2 ) end local values = mw.text.split( filter, ',' ) -- split values: '1,3-5' to {'1','3-5'} for _, value in pairs( values ) do value = mw.text.trim( value ) local min, max = mw.ustring.match( value, '^(%d+)%s*[-–—]%s*(%d+)$' ) -- '3-5' to min=3 max=5 if not max then min, max = string.match( value, '^((%d+))$' ) end -- '1' to min=1 max=1 if max then for i = min, max do filters[ i ] = true end else filters[ value ] = true -- if we reach this point, the string had the form 'a,b,c' rather than '1,2,3' end end local filter = {cache = {}, terms = filters} return filter, isBlacklist end -- Helper function to see if a value matches any of the given filters function Excerpt.matchFilter( value, filter ) if value == nil then return false elseif type(value) == "number" then return filter.terms[value] else local cached = filter.cache[value] if cached ~= nil then return cached end local lang = mw.language.getContentLanguage() local lcvalue = lang:lcfirst(value) local ucvalue = lang:ucfirst(value) for term in pairs( filter.terms ) do if value == tostring(term) or type(term) == "string" and ( lcvalue == term or ucvalue == term or mw.ustring.match( value, term ) ) then filter.cache[value] = true return true end end filter.cache[value] = false end end return Excerpt ka2e1ezo2sdes03rw91k5qedbiixxgj 797361 797360 2026-01-17T13:43:34Z en>Sophivorus 0 Fix minor Lua linting errors 797361 Scribunto text/plain -- Module:Excerpt implements the Excerpt template -- Documentation and master version: https://en.wikipedia.org/wiki/Module:Excerpt -- Authors: User:Sophivorus, User:Certes, User:Aidan9382 & others -- License: CC-BY-SA-3.0 local parser = require( 'Module:WikitextParser' ) local yesno = require( 'Module:Yesno' ) local ok, config = pcall( require, 'Module:Excerpt/config' ) if not ok then config = {} end local Excerpt = {} -- Main entry point for templates function Excerpt.main( frame ) -- Make sure the requested page exists and get the wikitext local page = Excerpt.getArg( 1 ) if not page or page == '{{{1}}}' then return Excerpt.getError( 'no-page' ) end local title = mw.title.new( page ) if not title then return Excerpt.getError( 'invalid-title', page ) end local fragment = title.fragment -- save for later if title.isRedirect then title = title.redirectTarget if fragment == "" then fragment = title.fragment -- page merge potential end end if not title.exists then return Excerpt.getError( 'page-not-found', page ) end page = title.prefixedText local wikitext = title:getContent() -- Get the template params and process them local params = { hat = yesno( Excerpt.getArg( 'hat', true ) ), this = Excerpt.getArg( 'this' ), only = Excerpt.getArg( 'only' ), files = Excerpt.getArg( 'files', Excerpt.getArg( 'file' ) ), lists = Excerpt.getArg( 'lists', Excerpt.getArg( 'list' ) ), tables = Excerpt.getArg( 'tables', Excerpt.getArg( 'table' ) ), templates = Excerpt.getArg( 'templates', Excerpt.getArg( 'template' ) ), paragraphs = Excerpt.getArg( 'paragraphs', Excerpt.getArg( 'paragraph' ) ), references = yesno( Excerpt.getArg( 'references', true ) ), subsections = yesno( Excerpt.getArg( 'subsections', false ) ), links = yesno( Excerpt.getArg( 'links', true ) ), bold = yesno( Excerpt.getArg( 'bold', false ) ), briefDates = yesno( Excerpt.getArg( 'briefdates', false ) ), inline = yesno( Excerpt.getArg( 'inline' ) ), quote = yesno( Excerpt.getArg( 'quote' ) ), more = yesno( Excerpt.getArg( 'more' ) ), class = Excerpt.getArg( 'class' ), track = yesno( Excerpt.getArg( 'track', true ) ), displayTitle = Excerpt.getArg( 'displaytitle', page ), } -- Make sure the requested section exists and get the excerpt local excerpt local section = Excerpt.getArg( 2, fragment ) section = mw.text.trim( section ) if section == '' then section = nil end if section then excerpt = parser.getSectionTag( wikitext, section ) if not excerpt then if params.subsections then excerpt = parser.getSection( wikitext, section ) else local sections = parser.getSections( wikitext ) excerpt = sections[ section ] end end if not excerpt then return Excerpt.getError( 'section-not-found', section ) end if excerpt == '' then return Excerpt.getError( 'section-empty', section ) end else excerpt = parser.getLead( wikitext ) if excerpt == '' then return Excerpt.getError( 'lead-empty' ) end end -- Remove noinclude bits excerpt = excerpt:gsub( '<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>', '' ) -- Filter various elements from the excerpt excerpt = Excerpt.filterFiles( excerpt, params.files ) excerpt = Excerpt.filterLists( excerpt, params.lists ) excerpt = Excerpt.filterTables( excerpt, params.tables ) excerpt = Excerpt.filterParagraphs( excerpt, params.paragraphs ) -- If no file is found, try to get one from the infobox if ( params.only == 'file' or params.only == 'files' or not params.only and ( not params.files or params.files ~= '0' ) ) -- caller asked for files and not section -- and we're in the lead section and config.captions -- and we have the config option required to try finding files in infoboxes and #parser.getFiles( excerpt ) == 0 -- and there're no files in the excerpt then excerpt = Excerpt.addInfoboxFile( excerpt ) end -- Filter the templates by appending the templates blacklist to the templates filter if config.blacklist then local blacklist = table.concat( config.blacklist, ',' ) if params.templates then if string.sub( params.templates, 1, 1 ) == '-' then params.templates = params.templates .. ',' .. blacklist end else params.templates = '-' .. blacklist end end excerpt = Excerpt.filterTemplates( excerpt, params.templates ) -- Leave only the requested elements if params.only == 'file' or params.only == 'files' then local files = parser.getFiles( excerpt ) excerpt = params.only == 'file' and files[1] or table.concat( files, '\n\n' ) end if params.only == 'list' or params.only == 'lists' then local lists = parser.getLists( excerpt ) excerpt = params.only == 'list' and lists[1] or table.concat( lists, '\n\n' ) end if params.only == 'table' or params.only == 'tables' then local tables = parser.getTables( excerpt ) excerpt = params.only == 'table' and tables[1] or table.concat( tables, '\n\n' ) end if params.only == 'paragraph' or params.only == 'paragraphs' then local paragraphs = parser.getParagraphs( excerpt ) excerpt = params.only == 'paragraph' and paragraphs[1] or table.concat( paragraphs, '\n\n' ) end if params.only == 'template' or params.only == 'templates' then local templates = parser.getTemplates( excerpt ) excerpt = params.only == 'template' and templates[1] or table.concat( templates, '\n\n' ) end -- @todo Make more robust and move downwards if params.briefDates then excerpt = Excerpt.fixDates( excerpt ) end -- Remove unwanted elements excerpt = Excerpt.removeComments( excerpt ) excerpt = Excerpt.removeSelfLinks( excerpt ) excerpt = Excerpt.removeNonFreeFiles( excerpt ) excerpt = Excerpt.removeBehaviorSwitches( excerpt ) -- Fix or remove the references if params.references then excerpt = Excerpt.fixReferences( excerpt, page, wikitext ) else excerpt = Excerpt.removeReferences( excerpt ) end -- Remove wikilinks if not params.links then excerpt = Excerpt.removeLinks( excerpt ) end -- Link the bold text near the start of most leads and then remove it if not section then excerpt = Excerpt.linkBold( excerpt, page ) end if not params.bold then excerpt = Excerpt.removeBold( excerpt ) end -- Remove extra line breaks but leave one before and after so the parser interprets lists, tables, etc. correctly excerpt = excerpt:gsub( '\n\n\n+', '\n\n' ) excerpt = mw.text.trim( excerpt ) excerpt = '\n' .. excerpt .. '\n' -- Remove nested categories excerpt = frame:preprocess( excerpt ) excerpt = Excerpt.removeCategories( excerpt ) -- Add tracking categories if params.track and config.categories then excerpt = Excerpt.addTrackingCategories( excerpt ) end -- Build the final output if params.inline then return mw.text.trim( excerpt ) end local tag = params.quote and 'blockquote' or 'div' local block = mw.html.create( tag ):addClass( 'excerpt-block' ):addClass( params.class ) if config.styles then local styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) block:node( styles ) end if params.hat then local hat = Excerpt.getHat( page, section, params ) block:node( hat ) end excerpt = mw.html.create( 'div' ):addClass( 'excerpt' ):wikitext( excerpt ) block:node( excerpt ) if params.more then local more = Excerpt.getReadMore( page, section ) block:node( more ) end return block end -- Filter the files in the given wikitext against the given filter function Excerpt.filterFiles( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local files = parser.getFiles( wikitext ) for index, file in pairs( files ) do local name = parser.getFileName( file ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( name, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( name, filters ) ) then wikitext = Excerpt.removeString( wikitext, file ) end end return wikitext end -- Filter the lists in the given wikitext against the given filter function Excerpt.filterLists( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local lists = parser.getLists( wikitext ) for index, list in pairs( lists ) do if isBlacklist and Excerpt.matchFilter( index, filters ) or not isBlacklist and not Excerpt.matchFilter( index, filters ) then wikitext = Excerpt.removeString( wikitext, list ) end end return wikitext end -- Filter the tables in the given wikitext against the given filter function Excerpt.filterTables( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local tables = parser.getTables( wikitext ) for index, tableWikitext in pairs( tables ) do local id = parser.getTableAttribute( tableWikitext, 'id' ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( id, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( id, filters ) ) then wikitext = Excerpt.removeString( wikitext, tableWikitext ) end end return wikitext end -- Filter the paragraphs in the given wikitext against the given filter function Excerpt.filterParagraphs( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local paragraphs = parser.getParagraphs( wikitext ) for index, paragraph in pairs( paragraphs ) do if isBlacklist and Excerpt.matchFilter( index, filters ) or not isBlacklist and not Excerpt.matchFilter( index, filters ) then wikitext = Excerpt.removeString( wikitext, paragraph ) end end return wikitext end -- Filter the templates in the given wikitext against the given filter function Excerpt.filterTemplates( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local templates = parser.getTemplates( wikitext ) for index, template in pairs( templates ) do local name = parser.getTemplateName( template ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( name, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( name, filters ) ) then wikitext = Excerpt.removeString( wikitext, template ) end end return wikitext end function Excerpt.addInfoboxFile( excerpt ) -- We cannot distinguish the infobox from the other templates, so we search them all local templates = parser.getTemplates( excerpt ) for _, template in pairs( templates ) do local parameters = parser.getTemplateParameters( template ) local file, captions, caption, cssClasses, cssClass for _, pair in pairs( config.captions ) do file = pair[1] file = parameters[file] if file and Excerpt.matchAny( file, '^.*%.', { '[Jj][Pp][Ee]?[Gg]', '[Pp][Nn][Gg]', '[Gg][Ii][Ff]', '[Ss][Vv][Gg]' }, '.*' ) then file = string.match( file, '%[?%[?.-:([^{|]+)%]?%]?' ) or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs( captions ) do if parameters[ p ] then caption = parameters[ p ] break end end -- Check for CSS classes -- We opt to use skin-invert-image instead of skin-invert -- in all other cases, the CSS provided in the infobox is used if pair[3] then cssClasses = pair[3] for _, p in pairs( cssClasses ) do if parameters[ p ] then cssClass = ( parameters[ p ] == 'skin-invert' ) and 'skin-invert-image' or parameters[ p ] break end end end local class = cssClass and ( '|class=' .. cssClass ) or '' return '[[File:' .. file .. class .. '|thumb|' .. ( caption or '' ) .. ']]' .. excerpt end end end return excerpt end function Excerpt.removeNonFreeFiles( wikitext ) local files = parser.getFiles( wikitext ) for _, file in pairs( files ) do local fileName = 'File:' .. parser.getFileName( file ) local fileTitle = mw.title.new( fileName ) if fileTitle then local fileDescription = fileTitle:getContent() if not fileDescription or fileDescription == '' then local frame = mw.getCurrentFrame() fileDescription = frame:preprocess( '{{' .. fileName .. '}}' ) -- try Commons end if fileDescription and string.match( fileDescription, '[Nn]on%-free' ) then wikitext = Excerpt.removeString( wikitext, file ) end end end return wikitext end function Excerpt.getHat( page, section, params ) local hat -- Build the text if params.this then hat = params.this elseif params.quote then hat = Excerpt.getMessage( 'this' ) elseif params.only then hat = Excerpt.getMessage( params.only ) else hat = Excerpt.getMessage( 'section' ) end hat = hat .. ' ' .. Excerpt.getMessage( 'excerpt' ) -- Build the link if section then hat = hat .. ' [[:' .. page .. '#' .. mw.uri.anchorEncode( section ) .. '|' .. params.displayTitle .. ' § ' .. section:gsub( '%[%[([^]|]+)|?[^]]*%]%]', '%1' ) .. ']].' -- remove nested links else hat = hat .. ' [[:' .. page .. '|' .. params.displayTitle .. ']].' end -- Build the edit link local title = mw.title.new( page ) local editUrl = title:fullUrl( 'action=edit' ) hat = hat .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. editUrl .. ' ' .. mw.message.new( 'editsection' ):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' if config.hat then local frame = mw.getCurrentFrame() hat = config.hat .. hat .. '}}' hat = frame:preprocess( hat ) else hat = mw.html.create( 'div' ):addClass( 'dablink excerpt-hat' ):wikitext( hat ) end return hat end function Excerpt.getReadMore( page, section ) local link = "'''[[" .. page if section then link = link .. '#' .. section end local text = Excerpt.getMessage( 'more' ) link = link .. '|' .. text .. "]]'''" link = mw.html.create( 'div' ):addClass( 'noprint excerpt-more' ):wikitext( link ) return link end -- Fix birth and death dates, but only in the first paragraph -- @todo Use parser.getParagraphs() to get the first paragraph function Excerpt.fixDates( excerpt ) local start = 1 -- skip initial templates local s local e = 0 repeat start = e + 1 s, e = mw.ustring.find( excerpt, '%s*%b{}%s*', start ) until not s or s > start s, e = mw.ustring.find( excerpt, '%b()', start ) -- get (...), which may be (year–year) if s and s < start + 100 then -- look only near the start local excerptStart = mw.ustring.sub( excerpt, s, e ) local year1, conjunction, year2 = string.match( excerptStart, '(%d%d%d+)(.-)(%d%d%d+)' ) if year1 and year2 and ( string.match( conjunction, '[%-–—]' ) or string.match( conjunction, '{{%s*[sS]nd%s*}}' ) ) then local y1 = tonumber( year1 ) local y2 = tonumber( year2 ) if y2 > y1 and y2 < y1 + 125 and y1 <= tonumber( os.date( '%Y' ) ) then excerpt = mw.ustring.sub( excerpt, 1, s ) .. year1 .. '–' .. year2 .. mw.ustring.sub( excerpt, e ) end end end return excerpt end -- Replace the first call to each reference defined outside of the excerpt for the full reference, to prevent undefined references -- Then prefix the page title to the reference names to prevent conflicts -- that is, replace <ref name="Foo"> for <ref name="Title of the article Foo"> -- and also <ref name="Foo" /> for <ref name="Title of the article Foo" /> -- also remove reference groups: <ref name="Foo" group="Bar"> for <ref name="Title of the article Foo"> -- and <ref group="Bar"> for <ref> -- @todo The current regex may fail in cases with both kinds of quotes, like <ref name="Darwin's book"> function Excerpt.fixReferences( excerpt, page, wikitext ) local references = parser.getReferences( excerpt ) local fixed = {} for _, reference in pairs( references ) do local name = parser.getTagAttribute( reference, 'name' ) if not fixed[ name ] then -- fix each reference only once local content = parser.getTagContent( reference ) if not content then -- reference is self-closing local full = parser.getReference( excerpt, name ) if not full then -- the reference is not defined in the excerpt full = parser.getReference( wikitext, name ) if full then excerpt = excerpt:gsub( Excerpt.escapeString( reference ), Excerpt.escapeString( full ), 1 ) end table.insert( fixed, name ) end end end end -- Prepend the page title to the reference names to prevent conflicts with other references in the transcluding page excerpt = excerpt:gsub( '< *[Rr][Ee][Ff][^>]*name *= *["\']?([^"\'>/]+)["\']?[^>/]*(/?) *>', '<ref name="' .. page:gsub( '"', '' ) .. ' %1"%2>' ) -- Remove reference groups because they don't apply to the transcluding page excerpt = excerpt:gsub( '< *[Rr][Ee][Ff] *group *= *["\']?[^"\'>/]+["\'] *>', '<ref>' ) return excerpt end function Excerpt.removeReferences( excerpt ) local references = parser.getReferences( excerpt ) for _, reference in pairs( references ) do excerpt = Excerpt.removeString( excerpt, reference ) end return excerpt end function Excerpt.removeCategories( excerpt ) local categories = parser.getCategories( excerpt ) for _, category in pairs( categories ) do excerpt = Excerpt.removeString( excerpt, category ) end return excerpt end function Excerpt.removeBehaviorSwitches( excerpt ) return excerpt:gsub( '__[A-Z]+__', '' ) end function Excerpt.removeComments( excerpt ) return excerpt:gsub( '<!%-%-.-%-%->', '' ) end function Excerpt.removeBold( excerpt ) return excerpt:gsub( "'''", '' ) end function Excerpt.removeLinks( excerpt ) local links = parser.getLinks( excerpt ) for _, link in pairs( links ) do excerpt = Excerpt.removeString( excerpt, link ) end return excerpt end -- @todo Use parser.getLinks function Excerpt.removeSelfLinks( excerpt ) local lang = mw.language.getContentLanguage() local page = Excerpt.escapeString( mw.title.getCurrentTitle().prefixedText ) local ucpage = lang:ucfirst( page ) local lcpage = lang:lcfirst( page ) excerpt = excerpt :gsub( '%[%[(' .. ucpage .. ')%]%]', '%1' ) :gsub( '%[%[(' .. lcpage .. ')%]%]', '%1' ) :gsub( '%[%[' .. ucpage .. '|([^]]+)%]%]', '%1' ) :gsub( '%[%[' .. lcpage .. '|([^]]+)%]%]', '%1' ) return excerpt end -- Replace the bold title or synonym near the start of the page by a link to the page function Excerpt.linkBold( excerpt, page ) local lang = mw.language.getContentLanguage() local position = mw.ustring.find( excerpt, "'''" .. lang:ucfirst( page ) .. "'''", 1, true ) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find( excerpt, "'''" .. lang:lcfirst( page ) .. "'''", 1, true ) -- plain search: special characters in page represent themselves if position then local length = mw.ustring.len( page ) excerpt = mw.ustring.sub( excerpt, 1, position + 2 ) .. '[[' .. mw.ustring.sub( excerpt, position + 3, position + length + 2 ) .. ']]' .. mw.ustring.sub( excerpt, position + length + 3, -1 ) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) excerpt = mw.ustring.gsub( excerpt, "'''(.-'*)'''", function ( text ) if not string.find( text, '%[' ) and not string.find( text, '%{' ) then -- if not wikilinked or some weird template return "'''[[" .. page .. '|' .. text .. "]]'''" -- replace '''Foo''' by '''[[page|Foo]]''' else return nil -- instruct gsub to make no change end end, 1 ) -- terminates the anonymous replacement function passed to gsub end return excerpt end function Excerpt.addTrackingCategories( excerpt ) local currentTitle = mw.title.getCurrentTitle() local addedCategories = false local contentCategory = config.categories.content if contentCategory and currentTitle.isContentPage then addedCategories = true excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ currentTitle.namespace ] if namespaceCategory then addedCategories = true excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end if addedCategories then excerpt = excerpt .. '\n' end return excerpt end -- Helper method to match from a list of regular expressions -- Like so: match pre..list[1]..post or pre..list[2]..post or ... function Excerpt.matchAny( text, pre, list, post, init ) for i = 1, #list do local match = { mw.ustring.match( text, pre .. list[ i ] .. post, init ) } if match[1] then return unpack( match ) end end return nil end -- Helper function to get arguments -- args from Lua calls have priority over parent args from template function Excerpt.getArg( key, default ) local frame = mw.getCurrentFrame() for k, value in pairs( frame:getParent().args ) do if k == key and mw.text.trim( value ) ~= '' then return value end end for k, value in pairs( frame.args ) do if k == key and mw.text.trim( value ) ~= '' then return value end end return default end -- Helper method to get an error message -- This method also categorizes the current page in one of the configured error categories function Excerpt.getError( key, value ) local message = Excerpt.getMessage( 'error-' .. key, value ) local markup = mw.html.create( 'div' ):addClass( 'error' ):wikitext( message ) if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then markup:node( '[[Category:' .. config.categories.errors .. ']]' ) end return markup end -- Helper method to get a localized message -- This method uses Module:TNT to get localized messages from https://commons.wikimedia.org/wiki/Data:I18n/Module:Excerpt.tab -- If Module:TNT is not available or the localized message does not exist, the key is returned instead function Excerpt.getMessage( key, value ) local ok, TNT = pcall( require, 'Module:TNT' ) if not ok then return key end local ok2, message = pcall( TNT.format, 'I18n/Module:Excerpt.tab', key, value ) if not ok2 then return key end return message end -- Helper method to escape a string for use in regexes function Excerpt.escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Helper method to remove a string from a text -- @param text Text from where to remove the string -- @param str String to remove -- @return The given text with the string removed function Excerpt.removeString( text, str ) local pattern = Excerpt.escapeString( str ) if #pattern > 9999 then -- strings longer than 10000 bytes can't be put into regexes pattern = Excerpt.escapeString( mw.ustring.sub( str, 1, 999 ) ) .. '.-' .. Excerpt.escapeString( mw.ustring.sub( str, -999 ) ) end return text:gsub( pattern, '' ) end -- Helper method to convert a comma-separated list of numbers or min-max ranges into a list of booleans -- @param filter Required. Comma-separated list of numbers or min-max ranges, for example '1,3-5' -- @return Map from integers to booleans, for example {1=true,2=false,3=true,4=true,5=true} -- @return Boolean indicating whether the filters should be treated as a blacklist or not -- @note Merging this into matchFilter is possible, but way too inefficient function Excerpt.parseFilter( filter ) local filters = {} local isBlacklist = false if string.sub( filter, 1, 1 ) == '-' then isBlacklist = true filter = string.sub( filter, 2 ) end local values = mw.text.split( filter, ',' ) -- split values: '1,3-5' to {'1','3-5'} for _, value in pairs( values ) do value = mw.text.trim( value ) local min, max = mw.ustring.match( value, '^(%d+)%s*[-–—]%s*(%d+)$' ) -- '3-5' to min=3 max=5 if not max then min, max = string.match( value, '^((%d+))$' ) end -- '1' to min=1 max=1 if max then for i = min, max do filters[ i ] = true end else filters[ value ] = true -- if we reach this point, the string had the form 'a,b,c' rather than '1,2,3' end end filter = { cache = {}, terms = filters } return filter, isBlacklist end -- Helper function to see if a value matches any of the given filters function Excerpt.matchFilter( value, filter ) if value == nil then return false elseif type(value) == "number" then return filter.terms[value] else local cached = filter.cache[value] if cached ~= nil then return cached end local lang = mw.language.getContentLanguage() local lcvalue = lang:lcfirst(value) local ucvalue = lang:ucfirst(value) for term in pairs( filter.terms ) do if value == tostring(term) or type(term) == "string" and ( lcvalue == term or ucvalue == term or mw.ustring.match( value, term ) ) then filter.cache[value] = true return true end end filter.cache[value] = false end end return Excerpt o9drtvsollvdhv0jwgian5wsaz75j92 797362 797361 2026-01-17T17:57:51Z en>Sophivorus 0 Fix bug where tables and lists are not parsed correctly when an infobox file is prepended 797362 Scribunto text/plain -- Module:Excerpt implements the Excerpt template -- Documentation and master version: https://en.wikipedia.org/wiki/Module:Excerpt -- Authors: User:Sophivorus, User:Certes, User:Aidan9382 & others -- License: CC-BY-SA-3.0 local parser = require( 'Module:WikitextParser' ) local yesno = require( 'Module:Yesno' ) local ok, config = pcall( require, 'Module:Excerpt/config' ) if not ok then config = {} end local Excerpt = {} -- Main entry point for templates function Excerpt.main( frame ) -- Make sure the requested page exists and get the wikitext local page = Excerpt.getArg( 1 ) if not page or page == '{{{1}}}' then return Excerpt.getError( 'no-page' ) end local title = mw.title.new( page ) if not title then return Excerpt.getError( 'invalid-title', page ) end local fragment = title.fragment -- save for later if title.isRedirect then title = title.redirectTarget if fragment == "" then fragment = title.fragment -- page merge potential end end if not title.exists then return Excerpt.getError( 'page-not-found', page ) end page = title.prefixedText local wikitext = title:getContent() -- Get the template params and process them local params = { hat = yesno( Excerpt.getArg( 'hat', true ) ), this = Excerpt.getArg( 'this' ), only = Excerpt.getArg( 'only' ), files = Excerpt.getArg( 'files', Excerpt.getArg( 'file' ) ), lists = Excerpt.getArg( 'lists', Excerpt.getArg( 'list' ) ), tables = Excerpt.getArg( 'tables', Excerpt.getArg( 'table' ) ), templates = Excerpt.getArg( 'templates', Excerpt.getArg( 'template' ) ), paragraphs = Excerpt.getArg( 'paragraphs', Excerpt.getArg( 'paragraph' ) ), references = yesno( Excerpt.getArg( 'references', true ) ), subsections = yesno( Excerpt.getArg( 'subsections', false ) ), links = yesno( Excerpt.getArg( 'links', true ) ), bold = yesno( Excerpt.getArg( 'bold', false ) ), briefDates = yesno( Excerpt.getArg( 'briefdates', false ) ), inline = yesno( Excerpt.getArg( 'inline' ) ), quote = yesno( Excerpt.getArg( 'quote' ) ), more = yesno( Excerpt.getArg( 'more' ) ), class = Excerpt.getArg( 'class' ), track = yesno( Excerpt.getArg( 'track', true ) ), displayTitle = Excerpt.getArg( 'displaytitle', page ), } -- Make sure the requested section exists and get the excerpt local excerpt local section = Excerpt.getArg( 2, fragment ) section = mw.text.trim( section ) if section == '' then section = nil end if section then excerpt = parser.getSectionTag( wikitext, section ) if not excerpt then if params.subsections then excerpt = parser.getSection( wikitext, section ) else local sections = parser.getSections( wikitext ) excerpt = sections[ section ] end end if not excerpt then return Excerpt.getError( 'section-not-found', section ) end if excerpt == '' then return Excerpt.getError( 'section-empty', section ) end else excerpt = parser.getLead( wikitext ) if excerpt == '' then return Excerpt.getError( 'lead-empty' ) end end -- Remove noinclude bits excerpt = excerpt:gsub( '<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>', '' ) -- Filter various elements from the excerpt excerpt = Excerpt.filterFiles( excerpt, params.files ) excerpt = Excerpt.filterLists( excerpt, params.lists ) excerpt = Excerpt.filterTables( excerpt, params.tables ) excerpt = Excerpt.filterParagraphs( excerpt, params.paragraphs ) -- If no file is found, try to get one from the infobox if ( params.only == 'file' or params.only == 'files' or not params.only and ( not params.files or params.files ~= '0' ) ) -- caller asked for files and not section -- and we're in the lead section and config.captions -- and we have the config option required to try finding files in infoboxes and #parser.getFiles( excerpt ) == 0 -- and there're no files in the excerpt then excerpt = Excerpt.addInfoboxFile( excerpt ) end -- Filter the templates by appending the templates blacklist to the templates filter if config.blacklist then local blacklist = table.concat( config.blacklist, ',' ) if params.templates then if string.sub( params.templates, 1, 1 ) == '-' then params.templates = params.templates .. ',' .. blacklist end else params.templates = '-' .. blacklist end end excerpt = Excerpt.filterTemplates( excerpt, params.templates ) -- Leave only the requested elements if params.only == 'file' or params.only == 'files' then local files = parser.getFiles( excerpt ) excerpt = params.only == 'file' and files[1] or table.concat( files, '\n\n' ) end if params.only == 'list' or params.only == 'lists' then local lists = parser.getLists( excerpt ) excerpt = params.only == 'list' and lists[1] or table.concat( lists, '\n\n' ) end if params.only == 'table' or params.only == 'tables' then local tables = parser.getTables( excerpt ) excerpt = params.only == 'table' and tables[1] or table.concat( tables, '\n\n' ) end if params.only == 'paragraph' or params.only == 'paragraphs' then local paragraphs = parser.getParagraphs( excerpt ) excerpt = params.only == 'paragraph' and paragraphs[1] or table.concat( paragraphs, '\n\n' ) end if params.only == 'template' or params.only == 'templates' then local templates = parser.getTemplates( excerpt ) excerpt = params.only == 'template' and templates[1] or table.concat( templates, '\n\n' ) end -- @todo Make more robust and move downwards if params.briefDates then excerpt = Excerpt.fixDates( excerpt ) end -- Remove unwanted elements excerpt = Excerpt.removeComments( excerpt ) excerpt = Excerpt.removeSelfLinks( excerpt ) excerpt = Excerpt.removeNonFreeFiles( excerpt ) excerpt = Excerpt.removeBehaviorSwitches( excerpt ) -- Fix or remove the references if params.references then excerpt = Excerpt.fixReferences( excerpt, page, wikitext ) else excerpt = Excerpt.removeReferences( excerpt ) end -- Remove wikilinks if not params.links then excerpt = Excerpt.removeLinks( excerpt ) end -- Link the bold text near the start of most leads and then remove it if not section then excerpt = Excerpt.linkBold( excerpt, page ) end if not params.bold then excerpt = Excerpt.removeBold( excerpt ) end -- Remove extra line breaks but leave one before and after so the parser interprets lists, tables, etc. correctly excerpt = excerpt:gsub( '\n\n\n+', '\n\n' ) excerpt = mw.text.trim( excerpt ) excerpt = '\n' .. excerpt .. '\n' -- Remove nested categories excerpt = frame:preprocess( excerpt ) excerpt = Excerpt.removeCategories( excerpt ) -- Add tracking categories if params.track and config.categories then excerpt = Excerpt.addTrackingCategories( excerpt ) end -- Build the final output if params.inline then return mw.text.trim( excerpt ) end local tag = params.quote and 'blockquote' or 'div' local block = mw.html.create( tag ):addClass( 'excerpt-block' ):addClass( params.class ) if config.styles then local styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) block:node( styles ) end if params.hat then local hat = Excerpt.getHat( page, section, params ) block:node( hat ) end excerpt = mw.html.create( 'div' ):addClass( 'excerpt' ):wikitext( excerpt ) block:node( excerpt ) if params.more then local more = Excerpt.getReadMore( page, section ) block:node( more ) end return block end -- Filter the files in the given wikitext against the given filter function Excerpt.filterFiles( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local files = parser.getFiles( wikitext ) for index, file in pairs( files ) do local name = parser.getFileName( file ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( name, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( name, filters ) ) then wikitext = Excerpt.removeString( wikitext, file ) end end return wikitext end -- Filter the lists in the given wikitext against the given filter function Excerpt.filterLists( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local lists = parser.getLists( wikitext ) for index, list in pairs( lists ) do if isBlacklist and Excerpt.matchFilter( index, filters ) or not isBlacklist and not Excerpt.matchFilter( index, filters ) then wikitext = Excerpt.removeString( wikitext, list ) end end return wikitext end -- Filter the tables in the given wikitext against the given filter function Excerpt.filterTables( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local tables = parser.getTables( wikitext ) for index, tableWikitext in pairs( tables ) do local id = parser.getTableAttribute( tableWikitext, 'id' ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( id, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( id, filters ) ) then wikitext = Excerpt.removeString( wikitext, tableWikitext ) end end return wikitext end -- Filter the paragraphs in the given wikitext against the given filter function Excerpt.filterParagraphs( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local paragraphs = parser.getParagraphs( wikitext ) for index, paragraph in pairs( paragraphs ) do if isBlacklist and Excerpt.matchFilter( index, filters ) or not isBlacklist and not Excerpt.matchFilter( index, filters ) then wikitext = Excerpt.removeString( wikitext, paragraph ) end end return wikitext end -- Filter the templates in the given wikitext against the given filter function Excerpt.filterTemplates( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local templates = parser.getTemplates( wikitext ) for index, template in pairs( templates ) do local name = parser.getTemplateName( template ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( name, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( name, filters ) ) then wikitext = Excerpt.removeString( wikitext, template ) end end return wikitext end function Excerpt.addInfoboxFile( excerpt ) -- We cannot distinguish the infobox from the other templates, so we search them all local templates = parser.getTemplates( excerpt ) for _, template in pairs( templates ) do local parameters = parser.getTemplateParameters( template ) local file, captions, caption, cssClasses, cssClass for _, pair in pairs( config.captions ) do file = pair[1] file = parameters[file] if file and Excerpt.matchAny( file, '^.*%.', { '[Jj][Pp][Ee]?[Gg]', '[Pp][Nn][Gg]', '[Gg][Ii][Ff]', '[Ss][Vv][Gg]' }, '.*' ) then file = string.match( file, '%[?%[?.-:([^{|]+)%]?%]?' ) or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs( captions ) do if parameters[ p ] then caption = parameters[ p ] break end end -- Check for CSS classes -- We opt to use skin-invert-image instead of skin-invert -- in all other cases, the CSS provided in the infobox is used if pair[3] then cssClasses = pair[3] for _, p in pairs( cssClasses ) do if parameters[ p ] then cssClass = ( parameters[ p ] == 'skin-invert' ) and 'skin-invert-image' or parameters[ p ] break end end end local class = cssClass and ( '|class=' .. cssClass ) or '' return '[[File:' .. file .. class .. '|thumb|' .. ( caption or '' ) .. ']]\n' .. excerpt end end end return excerpt end function Excerpt.removeNonFreeFiles( wikitext ) local files = parser.getFiles( wikitext ) for _, file in pairs( files ) do local fileName = 'File:' .. parser.getFileName( file ) local fileTitle = mw.title.new( fileName ) if fileTitle then local fileDescription = fileTitle:getContent() if not fileDescription or fileDescription == '' then local frame = mw.getCurrentFrame() fileDescription = frame:preprocess( '{{' .. fileName .. '}}' ) -- try Commons end if fileDescription and string.match( fileDescription, '[Nn]on%-free' ) then wikitext = Excerpt.removeString( wikitext, file ) end end end return wikitext end function Excerpt.getHat( page, section, params ) local hat -- Build the text if params.this then hat = params.this elseif params.quote then hat = Excerpt.getMessage( 'this' ) elseif params.only then hat = Excerpt.getMessage( params.only ) else hat = Excerpt.getMessage( 'section' ) end hat = hat .. ' ' .. Excerpt.getMessage( 'excerpt' ) -- Build the link if section then hat = hat .. ' [[:' .. page .. '#' .. mw.uri.anchorEncode( section ) .. '|' .. params.displayTitle .. ' § ' .. section:gsub( '%[%[([^]|]+)|?[^]]*%]%]', '%1' ) .. ']].' -- remove nested links else hat = hat .. ' [[:' .. page .. '|' .. params.displayTitle .. ']].' end -- Build the edit link local title = mw.title.new( page ) local editUrl = title:fullUrl( 'action=edit' ) hat = hat .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. editUrl .. ' ' .. mw.message.new( 'editsection' ):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' if config.hat then local frame = mw.getCurrentFrame() hat = config.hat .. hat .. '}}' hat = frame:preprocess( hat ) else hat = mw.html.create( 'div' ):addClass( 'dablink excerpt-hat' ):wikitext( hat ) end return hat end function Excerpt.getReadMore( page, section ) local link = "'''[[" .. page if section then link = link .. '#' .. section end local text = Excerpt.getMessage( 'more' ) link = link .. '|' .. text .. "]]'''" link = mw.html.create( 'div' ):addClass( 'noprint excerpt-more' ):wikitext( link ) return link end -- Fix birth and death dates, but only in the first paragraph -- @todo Use parser.getParagraphs() to get the first paragraph function Excerpt.fixDates( excerpt ) local start = 1 -- skip initial templates local s local e = 0 repeat start = e + 1 s, e = mw.ustring.find( excerpt, '%s*%b{}%s*', start ) until not s or s > start s, e = mw.ustring.find( excerpt, '%b()', start ) -- get (...), which may be (year–year) if s and s < start + 100 then -- look only near the start local excerptStart = mw.ustring.sub( excerpt, s, e ) local year1, conjunction, year2 = string.match( excerptStart, '(%d%d%d+)(.-)(%d%d%d+)' ) if year1 and year2 and ( string.match( conjunction, '[%-–—]' ) or string.match( conjunction, '{{%s*[sS]nd%s*}}' ) ) then local y1 = tonumber( year1 ) local y2 = tonumber( year2 ) if y2 > y1 and y2 < y1 + 125 and y1 <= tonumber( os.date( '%Y' ) ) then excerpt = mw.ustring.sub( excerpt, 1, s ) .. year1 .. '–' .. year2 .. mw.ustring.sub( excerpt, e ) end end end return excerpt end -- Replace the first call to each reference defined outside of the excerpt for the full reference, to prevent undefined references -- Then prefix the page title to the reference names to prevent conflicts -- that is, replace <ref name="Foo"> for <ref name="Title of the article Foo"> -- and also <ref name="Foo" /> for <ref name="Title of the article Foo" /> -- also remove reference groups: <ref name="Foo" group="Bar"> for <ref name="Title of the article Foo"> -- and <ref group="Bar"> for <ref> -- @todo The current regex may fail in cases with both kinds of quotes, like <ref name="Darwin's book"> function Excerpt.fixReferences( excerpt, page, wikitext ) local references = parser.getReferences( excerpt ) local fixed = {} for _, reference in pairs( references ) do local name = parser.getTagAttribute( reference, 'name' ) if not fixed[ name ] then -- fix each reference only once local content = parser.getTagContent( reference ) if not content then -- reference is self-closing local full = parser.getReference( excerpt, name ) if not full then -- the reference is not defined in the excerpt full = parser.getReference( wikitext, name ) if full then excerpt = excerpt:gsub( Excerpt.escapeString( reference ), Excerpt.escapeString( full ), 1 ) end table.insert( fixed, name ) end end end end -- Prepend the page title to the reference names to prevent conflicts with other references in the transcluding page excerpt = excerpt:gsub( '< *[Rr][Ee][Ff][^>]*name *= *["\']?([^"\'>/]+)["\']?[^>/]*(/?) *>', '<ref name="' .. page:gsub( '"', '' ) .. ' %1"%2>' ) -- Remove reference groups because they don't apply to the transcluding page excerpt = excerpt:gsub( '< *[Rr][Ee][Ff] *group *= *["\']?[^"\'>/]+["\'] *>', '<ref>' ) return excerpt end function Excerpt.removeReferences( excerpt ) local references = parser.getReferences( excerpt ) for _, reference in pairs( references ) do excerpt = Excerpt.removeString( excerpt, reference ) end return excerpt end function Excerpt.removeCategories( excerpt ) local categories = parser.getCategories( excerpt ) for _, category in pairs( categories ) do excerpt = Excerpt.removeString( excerpt, category ) end return excerpt end function Excerpt.removeBehaviorSwitches( excerpt ) return excerpt:gsub( '__[A-Z]+__', '' ) end function Excerpt.removeComments( excerpt ) return excerpt:gsub( '<!%-%-.-%-%->', '' ) end function Excerpt.removeBold( excerpt ) return excerpt:gsub( "'''", '' ) end function Excerpt.removeLinks( excerpt ) local links = parser.getLinks( excerpt ) for _, link in pairs( links ) do excerpt = Excerpt.removeString( excerpt, link ) end return excerpt end -- @todo Use parser.getLinks function Excerpt.removeSelfLinks( excerpt ) local lang = mw.language.getContentLanguage() local page = Excerpt.escapeString( mw.title.getCurrentTitle().prefixedText ) local ucpage = lang:ucfirst( page ) local lcpage = lang:lcfirst( page ) excerpt = excerpt :gsub( '%[%[(' .. ucpage .. ')%]%]', '%1' ) :gsub( '%[%[(' .. lcpage .. ')%]%]', '%1' ) :gsub( '%[%[' .. ucpage .. '|([^]]+)%]%]', '%1' ) :gsub( '%[%[' .. lcpage .. '|([^]]+)%]%]', '%1' ) return excerpt end -- Replace the bold title or synonym near the start of the page by a link to the page function Excerpt.linkBold( excerpt, page ) local lang = mw.language.getContentLanguage() local position = mw.ustring.find( excerpt, "'''" .. lang:ucfirst( page ) .. "'''", 1, true ) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find( excerpt, "'''" .. lang:lcfirst( page ) .. "'''", 1, true ) -- plain search: special characters in page represent themselves if position then local length = mw.ustring.len( page ) excerpt = mw.ustring.sub( excerpt, 1, position + 2 ) .. '[[' .. mw.ustring.sub( excerpt, position + 3, position + length + 2 ) .. ']]' .. mw.ustring.sub( excerpt, position + length + 3, -1 ) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) excerpt = mw.ustring.gsub( excerpt, "'''(.-'*)'''", function ( text ) if not string.find( text, '%[' ) and not string.find( text, '%{' ) then -- if not wikilinked or some weird template return "'''[[" .. page .. '|' .. text .. "]]'''" -- replace '''Foo''' by '''[[page|Foo]]''' else return nil -- instruct gsub to make no change end end, 1 ) -- terminates the anonymous replacement function passed to gsub end return excerpt end function Excerpt.addTrackingCategories( excerpt ) local currentTitle = mw.title.getCurrentTitle() local addedCategories = false local contentCategory = config.categories.content if contentCategory and currentTitle.isContentPage then addedCategories = true excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ currentTitle.namespace ] if namespaceCategory then addedCategories = true excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end if addedCategories then excerpt = excerpt .. '\n' end return excerpt end -- Helper method to match from a list of regular expressions -- Like so: match pre..list[1]..post or pre..list[2]..post or ... function Excerpt.matchAny( text, pre, list, post, init ) for i = 1, #list do local match = { mw.ustring.match( text, pre .. list[ i ] .. post, init ) } if match[1] then return unpack( match ) end end return nil end -- Helper function to get arguments -- args from Lua calls have priority over parent args from template function Excerpt.getArg( key, default ) local frame = mw.getCurrentFrame() for k, value in pairs( frame:getParent().args ) do if k == key and mw.text.trim( value ) ~= '' then return value end end for k, value in pairs( frame.args ) do if k == key and mw.text.trim( value ) ~= '' then return value end end return default end -- Helper method to get an error message -- This method also categorizes the current page in one of the configured error categories function Excerpt.getError( key, value ) local message = Excerpt.getMessage( 'error-' .. key, value ) local markup = mw.html.create( 'div' ):addClass( 'error' ):wikitext( message ) if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then markup:node( '[[Category:' .. config.categories.errors .. ']]' ) end return markup end -- Helper method to get a localized message -- This method uses Module:TNT to get localized messages from https://commons.wikimedia.org/wiki/Data:I18n/Module:Excerpt.tab -- If Module:TNT is not available or the localized message does not exist, the key is returned instead function Excerpt.getMessage( key, value ) local ok, TNT = pcall( require, 'Module:TNT' ) if not ok then return key end local ok2, message = pcall( TNT.format, 'I18n/Module:Excerpt.tab', key, value ) if not ok2 then return key end return message end -- Helper method to escape a string for use in regexes function Excerpt.escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Helper method to remove a string from a text -- @param text Text from where to remove the string -- @param str String to remove -- @return The given text with the string removed function Excerpt.removeString( text, str ) local pattern = Excerpt.escapeString( str ) if #pattern > 9999 then -- strings longer than 10000 bytes can't be put into regexes pattern = Excerpt.escapeString( mw.ustring.sub( str, 1, 999 ) ) .. '.-' .. Excerpt.escapeString( mw.ustring.sub( str, -999 ) ) end return text:gsub( pattern, '' ) end -- Helper method to convert a comma-separated list of numbers or min-max ranges into a list of booleans -- @param filter Required. Comma-separated list of numbers or min-max ranges, for example '1,3-5' -- @return Map from integers to booleans, for example {1=true,2=false,3=true,4=true,5=true} -- @return Boolean indicating whether the filters should be treated as a blacklist or not -- @note Merging this into matchFilter is possible, but way too inefficient function Excerpt.parseFilter( filter ) local filters = {} local isBlacklist = false if string.sub( filter, 1, 1 ) == '-' then isBlacklist = true filter = string.sub( filter, 2 ) end local values = mw.text.split( filter, ',' ) -- split values: '1,3-5' to {'1','3-5'} for _, value in pairs( values ) do value = mw.text.trim( value ) local min, max = mw.ustring.match( value, '^(%d+)%s*[-–—]%s*(%d+)$' ) -- '3-5' to min=3 max=5 if not max then min, max = string.match( value, '^((%d+))$' ) end -- '1' to min=1 max=1 if max then for i = min, max do filters[ i ] = true end else filters[ value ] = true -- if we reach this point, the string had the form 'a,b,c' rather than '1,2,3' end end filter = { cache = {}, terms = filters } return filter, isBlacklist end -- Helper function to see if a value matches any of the given filters function Excerpt.matchFilter( value, filter ) if value == nil then return false elseif type(value) == "number" then return filter.terms[value] else local cached = filter.cache[value] if cached ~= nil then return cached end local lang = mw.language.getContentLanguage() local lcvalue = lang:lcfirst(value) local ucvalue = lang:ucfirst(value) for term in pairs( filter.terms ) do if value == tostring(term) or type(term) == "string" and ( lcvalue == term or ucvalue == term or mw.ustring.match( value, term ) ) then filter.cache[value] = true return true end end filter.cache[value] = false end end return Excerpt sqwxoaaqa8kkfuqqm9rxo7n75q6qxtx 797363 797362 2026-01-18T16:48:00Z en>Sophivorus 0 Fix lint errors 797363 Scribunto text/plain -- Module:Excerpt implements the Excerpt template -- Documentation and master version: https://en.wikipedia.org/wiki/Module:Excerpt -- Authors: User:Sophivorus, User:Certes, User:Aidan9382 & others -- License: CC-BY-SA-3.0 local parser = require( 'Module:WikitextParser' ) local yesno = require( 'Module:Yesno' ) local ok, config = pcall( require, 'Module:Excerpt/config' ) if not ok then config = {} end local Excerpt = {} -- Main entry point for templates function Excerpt.main( frame ) -- Make sure the requested page exists and get the wikitext local page = Excerpt.getArg( 1 ) if not page or page == '{{{1}}}' then return Excerpt.getError( 'no-page' ) end local title = mw.title.new( page ) if not title then return Excerpt.getError( 'invalid-title', page ) end local fragment = title.fragment -- save for later if title.isRedirect then title = title.redirectTarget if fragment == "" then fragment = title.fragment -- page merge potential end end if not title.exists then return Excerpt.getError( 'page-not-found', page ) end page = title.prefixedText local wikitext = title:getContent() -- Get the template params and process them local params = { hat = yesno( Excerpt.getArg( 'hat', true ) ), this = Excerpt.getArg( 'this' ), only = Excerpt.getArg( 'only' ), files = Excerpt.getArg( 'files', Excerpt.getArg( 'file' ) ), lists = Excerpt.getArg( 'lists', Excerpt.getArg( 'list' ) ), tables = Excerpt.getArg( 'tables', Excerpt.getArg( 'table' ) ), templates = Excerpt.getArg( 'templates', Excerpt.getArg( 'template' ) ), paragraphs = Excerpt.getArg( 'paragraphs', Excerpt.getArg( 'paragraph' ) ), references = yesno( Excerpt.getArg( 'references', true ) ), subsections = yesno( Excerpt.getArg( 'subsections', false ) ), links = yesno( Excerpt.getArg( 'links', true ) ), bold = yesno( Excerpt.getArg( 'bold', false ) ), briefDates = yesno( Excerpt.getArg( 'briefdates', false ) ), inline = yesno( Excerpt.getArg( 'inline' ) ), quote = yesno( Excerpt.getArg( 'quote' ) ), more = yesno( Excerpt.getArg( 'more' ) ), class = Excerpt.getArg( 'class' ), track = yesno( Excerpt.getArg( 'track', true ) ), displayTitle = Excerpt.getArg( 'displaytitle', page ), } -- Make sure the requested section exists and get the excerpt local excerpt local section = Excerpt.getArg( 2, fragment ) section = mw.text.trim( section ) if section == '' then section = nil end if section then excerpt = parser.getSectionTag( wikitext, section ) if not excerpt then if params.subsections then excerpt = parser.getSection( wikitext, section ) else local sections = parser.getSections( wikitext ) excerpt = sections[ section ] end end if not excerpt then return Excerpt.getError( 'section-not-found', section ) end if excerpt == '' then return Excerpt.getError( 'section-empty', section ) end else excerpt = parser.getLead( wikitext ) if excerpt == '' then return Excerpt.getError( 'lead-empty' ) end end -- Remove noinclude bits excerpt = excerpt:gsub( '<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>', '' ) -- Filter various elements from the excerpt excerpt = Excerpt.filterFiles( excerpt, params.files ) excerpt = Excerpt.filterLists( excerpt, params.lists ) excerpt = Excerpt.filterTables( excerpt, params.tables ) excerpt = Excerpt.filterParagraphs( excerpt, params.paragraphs ) -- If no file is found, try to get one from the infobox if ( params.only == 'file' or params.only == 'files' or not params.only and ( not params.files or params.files ~= '0' ) ) -- caller asked for files and not section -- and we're in the lead section and config.captions -- and we have the config option required to try finding files in infoboxes and #parser.getFiles( excerpt ) == 0 -- and there're no files in the excerpt then excerpt = Excerpt.addInfoboxFile( excerpt ) end -- Filter the templates by appending the templates blacklist to the templates filter if config.blacklist then local blacklist = table.concat( config.blacklist, ',' ) if params.templates then if string.sub( params.templates, 1, 1 ) == '-' then params.templates = params.templates .. ',' .. blacklist end else params.templates = '-' .. blacklist end end excerpt = Excerpt.filterTemplates( excerpt, params.templates ) -- Leave only the requested elements if params.only == 'file' or params.only == 'files' then local files = parser.getFiles( excerpt ) excerpt = params.only == 'file' and files[1] or table.concat( files, '\n\n' ) end if params.only == 'list' or params.only == 'lists' then local lists = parser.getLists( excerpt ) excerpt = params.only == 'list' and lists[1] or table.concat( lists, '\n\n' ) end if params.only == 'table' or params.only == 'tables' then local tables = parser.getTables( excerpt ) excerpt = params.only == 'table' and tables[1] or table.concat( tables, '\n\n' ) end if params.only == 'paragraph' or params.only == 'paragraphs' then local paragraphs = parser.getParagraphs( excerpt ) excerpt = params.only == 'paragraph' and paragraphs[1] or table.concat( paragraphs, '\n\n' ) end if params.only == 'template' or params.only == 'templates' then local templates = parser.getTemplates( excerpt ) excerpt = params.only == 'template' and templates[1] or table.concat( templates, '\n\n' ) end -- @todo Make more robust and move downwards if params.briefDates then excerpt = Excerpt.fixDates( excerpt ) end -- Remove unwanted elements excerpt = Excerpt.removeComments( excerpt ) excerpt = Excerpt.removeSelfLinks( excerpt ) excerpt = Excerpt.removeNonFreeFiles( excerpt ) excerpt = Excerpt.removeBehaviorSwitches( excerpt ) -- Fix or remove the references if params.references then excerpt = Excerpt.fixReferences( excerpt, page, wikitext ) else excerpt = Excerpt.removeReferences( excerpt ) end -- Remove wikilinks if not params.links then excerpt = Excerpt.removeLinks( excerpt ) end -- Link the bold text near the start of most leads and then remove it if not section then excerpt = Excerpt.linkBold( excerpt, page ) end if not params.bold then excerpt = Excerpt.removeBold( excerpt ) end -- Remove extra line breaks but leave one before and after so the parser interprets lists, tables, etc. correctly excerpt = excerpt:gsub( '\n\n\n+', '\n\n' ) excerpt = mw.text.trim( excerpt ) excerpt = '\n' .. excerpt .. '\n' -- Remove nested categories excerpt = frame:preprocess( excerpt ) excerpt = Excerpt.removeCategories( excerpt ) -- Add tracking categories if params.track and config.categories then excerpt = Excerpt.addTrackingCategories( excerpt ) end -- Build the final output if params.inline then return mw.text.trim( excerpt ) end local tag = params.quote and 'blockquote' or 'div' local block = mw.html.create( tag ):addClass( 'excerpt-block' ):addClass( params.class ) if config.styles then local styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) block:node( styles ) end if params.hat then local hat = Excerpt.getHat( page, section, params ) block:node( hat ) end excerpt = mw.html.create( 'div' ):addClass( 'excerpt' ):wikitext( excerpt ) block:node( excerpt ) if params.more then local more = Excerpt.getReadMore( page, section ) block:node( more ) end return block end -- Filter the files in the given wikitext against the given filter function Excerpt.filterFiles( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local files = parser.getFiles( wikitext ) for index, file in pairs( files ) do local name = parser.getFileName( file ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( name, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( name, filters ) ) then wikitext = Excerpt.removeString( wikitext, file ) end end return wikitext end -- Filter the lists in the given wikitext against the given filter function Excerpt.filterLists( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local lists = parser.getLists( wikitext ) for index, list in pairs( lists ) do if isBlacklist and Excerpt.matchFilter( index, filters ) or not isBlacklist and not Excerpt.matchFilter( index, filters ) then wikitext = Excerpt.removeString( wikitext, list ) end end return wikitext end -- Filter the tables in the given wikitext against the given filter function Excerpt.filterTables( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local tables = parser.getTables( wikitext ) for index, tableWikitext in pairs( tables ) do local id = parser.getTableAttribute( tableWikitext, 'id' ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( id, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( id, filters ) ) then wikitext = Excerpt.removeString( wikitext, tableWikitext ) end end return wikitext end -- Filter the paragraphs in the given wikitext against the given filter function Excerpt.filterParagraphs( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local paragraphs = parser.getParagraphs( wikitext ) for index, paragraph in pairs( paragraphs ) do if isBlacklist and Excerpt.matchFilter( index, filters ) or not isBlacklist and not Excerpt.matchFilter( index, filters ) then wikitext = Excerpt.removeString( wikitext, paragraph ) end end return wikitext end -- Filter the templates in the given wikitext against the given filter function Excerpt.filterTemplates( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local templates = parser.getTemplates( wikitext ) for index, template in pairs( templates ) do local name = parser.getTemplateName( template ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( name, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( name, filters ) ) then wikitext = Excerpt.removeString( wikitext, template ) end end return wikitext end function Excerpt.addInfoboxFile( excerpt ) -- We cannot distinguish the infobox from the other templates, so we search them all local templates = parser.getTemplates( excerpt ) for _, template in pairs( templates ) do local parameters = parser.getTemplateParameters( template ) local file, captions, caption, cssClasses, cssClass for _, pair in pairs( config.captions ) do file = pair[1] file = parameters[file] if file and Excerpt.matchAny( file, '^.*%.', { '[Jj][Pp][Ee]?[Gg]', '[Pp][Nn][Gg]', '[Gg][Ii][Ff]', '[Ss][Vv][Gg]' }, '.*' ) then file = string.match( file, '%[?%[?.-:([^{|]+)%]?%]?' ) or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs( captions ) do if parameters[ p ] then caption = parameters[ p ] break end end -- Check for CSS classes -- We opt to use skin-invert-image instead of skin-invert -- in all other cases, the CSS provided in the infobox is used if pair[3] then cssClasses = pair[3] for _, p in pairs( cssClasses ) do if parameters[ p ] then cssClass = ( parameters[ p ] == 'skin-invert' ) and 'skin-invert-image' or parameters[ p ] break end end end local class = cssClass and ( '|class=' .. cssClass ) or '' return '[[File:' .. file .. class .. '|thumb|' .. ( caption or '' ) .. ']]\n' .. excerpt end end end return excerpt end function Excerpt.removeNonFreeFiles( wikitext ) local files = parser.getFiles( wikitext ) for _, file in pairs( files ) do local fileName = 'File:' .. parser.getFileName( file ) local fileTitle = mw.title.new( fileName ) if fileTitle then local fileDescription = fileTitle:getContent() if not fileDescription or fileDescription == '' then local frame = mw.getCurrentFrame() fileDescription = frame:preprocess( '{{' .. fileName .. '}}' ) -- try Commons end if fileDescription and string.match( fileDescription, '[Nn]on%-free' ) then wikitext = Excerpt.removeString( wikitext, file ) end end end return wikitext end function Excerpt.getHat( page, section, params ) local hat -- Build the text if params.this then hat = params.this elseif params.quote then hat = Excerpt.getMessage( 'this' ) elseif params.only then hat = Excerpt.getMessage( params.only ) else hat = Excerpt.getMessage( 'section' ) end hat = hat .. ' ' .. Excerpt.getMessage( 'excerpt' ) -- Build the link if section then hat = hat .. ' [[:' .. page .. '#' .. mw.uri.anchorEncode( section ) .. '|' .. params.displayTitle .. ' § ' .. section:gsub( '%[%[([^]|]+)|?[^]]*%]%]', '%1' ) .. ']].' -- remove nested links else hat = hat .. ' [[:' .. page .. '|' .. params.displayTitle .. ']].' end -- Build the edit link local title = mw.title.new( page ) local editUrl = title:fullUrl( 'action=edit' ) hat = hat .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. editUrl .. ' ' .. mw.message.new( 'editsection' ):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' if config.hat then local frame = mw.getCurrentFrame() hat = config.hat .. hat .. '}}' hat = frame:preprocess( hat ) else hat = mw.html.create( 'div' ):addClass( 'dablink excerpt-hat' ):wikitext( hat ) end return hat end function Excerpt.getReadMore( page, section ) local link = "'''[[" .. page if section then link = link .. '#' .. section end local text = Excerpt.getMessage( 'more' ) link = link .. '|' .. text .. "]]'''" link = mw.html.create( 'div' ):addClass( 'noprint excerpt-more' ):wikitext( link ) return link end -- Fix birth and death dates, but only in the first paragraph -- @todo Use parser.getParagraphs() to get the first paragraph function Excerpt.fixDates( excerpt ) local start local s local e = 0 repeat start = e + 1 s, e = mw.ustring.find( excerpt, '%s*%b{}%s*', start ) until not s or s > start s, e = mw.ustring.find( excerpt, '%b()', start ) -- get (...), which may be (year–year) if s and s < start + 100 then -- look only near the start local excerptStart = mw.ustring.sub( excerpt, s, e ) local year1, conjunction, year2 = string.match( excerptStart, '(%d%d%d+)(.-)(%d%d%d+)' ) if year1 and year2 and ( string.match( conjunction, '[%-–—]' ) or string.match( conjunction, '{{%s*[sS]nd%s*}}' ) ) then local y1 = tonumber( year1 ) local y2 = tonumber( year2 ) if y2 > y1 and y2 < y1 + 125 and y1 <= tonumber( os.date( '%Y' ) ) then excerpt = mw.ustring.sub( excerpt, 1, s ) .. year1 .. '–' .. year2 .. mw.ustring.sub( excerpt, e ) end end end return excerpt end -- Replace the first call to each reference defined outside of the excerpt for the full reference, to prevent undefined references -- Then prefix the page title to the reference names to prevent conflicts -- that is, replace <ref name="Foo"> for <ref name="Title of the article Foo"> -- and also <ref name="Foo" /> for <ref name="Title of the article Foo" /> -- also remove reference groups: <ref name="Foo" group="Bar"> for <ref name="Title of the article Foo"> -- and <ref group="Bar"> for <ref> -- @todo The current regex may fail in cases with both kinds of quotes, like <ref name="Darwin's book"> function Excerpt.fixReferences( excerpt, page, wikitext ) local references = parser.getReferences( excerpt ) local fixed = {} for _, reference in pairs( references ) do local name = parser.getTagAttribute( reference, 'name' ) if not fixed[ name ] then -- fix each reference only once local content = parser.getTagContent( reference ) if not content then -- reference is self-closing local full = parser.getReference( excerpt, name ) if not full then -- the reference is not defined in the excerpt full = parser.getReference( wikitext, name ) if full then excerpt = excerpt:gsub( Excerpt.escapeString( reference ), Excerpt.escapeString( full ), 1 ) end table.insert( fixed, name ) end end end end -- Prepend the page title to the reference names to prevent conflicts with other references in the transcluding page excerpt = excerpt:gsub( '< *[Rr][Ee][Ff][^>]*name *= *["\']?([^"\'>/]+)["\']?[^>/]*(/?) *>', '<ref name="' .. page:gsub( '"', '' ) .. ' %1"%2>' ) -- Remove reference groups because they don't apply to the transcluding page excerpt = excerpt:gsub( '< *[Rr][Ee][Ff] *group *= *["\']?[^"\'>/]+["\'] *>', '<ref>' ) return excerpt end function Excerpt.removeReferences( excerpt ) local references = parser.getReferences( excerpt ) for _, reference in pairs( references ) do excerpt = Excerpt.removeString( excerpt, reference ) end return excerpt end function Excerpt.removeCategories( excerpt ) local categories = parser.getCategories( excerpt ) for _, category in pairs( categories ) do excerpt = Excerpt.removeString( excerpt, category ) end return excerpt end function Excerpt.removeBehaviorSwitches( excerpt ) return excerpt:gsub( '__[A-Z]+__', '' ) end function Excerpt.removeComments( excerpt ) return excerpt:gsub( '<!%-%-.-%-%->', '' ) end function Excerpt.removeBold( excerpt ) return excerpt:gsub( "'''", '' ) end function Excerpt.removeLinks( excerpt ) local links = parser.getLinks( excerpt ) for _, link in pairs( links ) do excerpt = Excerpt.removeString( excerpt, link ) end return excerpt end -- @todo Use parser.getLinks function Excerpt.removeSelfLinks( excerpt ) local lang = mw.language.getContentLanguage() local page = Excerpt.escapeString( mw.title.getCurrentTitle().prefixedText ) local ucpage = lang:ucfirst( page ) local lcpage = lang:lcfirst( page ) excerpt = excerpt :gsub( '%[%[(' .. ucpage .. ')%]%]', '%1' ) :gsub( '%[%[(' .. lcpage .. ')%]%]', '%1' ) :gsub( '%[%[' .. ucpage .. '|([^]]+)%]%]', '%1' ) :gsub( '%[%[' .. lcpage .. '|([^]]+)%]%]', '%1' ) return excerpt end -- Replace the bold title or synonym near the start of the page by a link to the page function Excerpt.linkBold( excerpt, page ) local lang = mw.language.getContentLanguage() local position = mw.ustring.find( excerpt, "'''" .. lang:ucfirst( page ) .. "'''", 1, true ) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find( excerpt, "'''" .. lang:lcfirst( page ) .. "'''", 1, true ) -- plain search: special characters in page represent themselves if position then local length = mw.ustring.len( page ) excerpt = mw.ustring.sub( excerpt, 1, position + 2 ) .. '[[' .. mw.ustring.sub( excerpt, position + 3, position + length + 2 ) .. ']]' .. mw.ustring.sub( excerpt, position + length + 3, -1 ) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) excerpt = mw.ustring.gsub( excerpt, "'''(.-'*)'''", function ( text ) if not string.find( text, '%[' ) and not string.find( text, '%{' ) then -- if not wikilinked or some weird template return "'''[[" .. page .. '|' .. text .. "]]'''" -- replace '''Foo''' by '''[[page|Foo]]''' else return nil -- instruct gsub to make no change end end, 1 ) -- terminates the anonymous replacement function passed to gsub end return excerpt end function Excerpt.addTrackingCategories( excerpt ) local currentTitle = mw.title.getCurrentTitle() local addedCategories = false local contentCategory = config.categories.content if contentCategory and currentTitle.isContentPage then addedCategories = true excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ currentTitle.namespace ] if namespaceCategory then addedCategories = true excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end if addedCategories then excerpt = excerpt .. '\n' end return excerpt end -- Helper method to match from a list of regular expressions -- Like so: match pre..list[1]..post or pre..list[2]..post or ... function Excerpt.matchAny( text, pre, list, post, init ) for i = 1, #list do local match = { mw.ustring.match( text, pre .. list[ i ] .. post, init ) } if match[1] then return unpack( match ) end end return nil end -- Helper function to get arguments -- args from Lua calls have priority over parent args from template function Excerpt.getArg( key, default ) local frame = mw.getCurrentFrame() for k, value in pairs( frame:getParent().args ) do if k == key and mw.text.trim( value ) ~= '' then return value end end for k, value in pairs( frame.args ) do if k == key and mw.text.trim( value ) ~= '' then return value end end return default end -- Helper method to get an error message -- This method also categorizes the current page in one of the configured error categories function Excerpt.getError( key, value ) local message = Excerpt.getMessage( 'error-' .. key, value ) local markup = mw.html.create( 'div' ):addClass( 'error' ):wikitext( message ) if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then markup:node( '[[Category:' .. config.categories.errors .. ']]' ) end return markup end -- Helper method to get a localized message -- This method uses Module:TNT to get localized messages from https://commons.wikimedia.org/wiki/Data:I18n/Module:Excerpt.tab -- If Module:TNT is not available or the localized message does not exist, the key is returned instead function Excerpt.getMessage( key, value ) local ok2, TNT = pcall( require, 'Module:TNT' ) if not ok2 then return key end local ok3, message = pcall( TNT.format, 'I18n/Module:Excerpt.tab', key, value ) if not ok3 then return key end return message end -- Helper method to escape a string for use in regexes function Excerpt.escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Helper method to remove a string from a text -- @param text Text from where to remove the string -- @param str String to remove -- @return The given text with the string removed function Excerpt.removeString( text, str ) local pattern = Excerpt.escapeString( str ) if #pattern > 9999 then -- strings longer than 10000 bytes can't be put into regexes pattern = Excerpt.escapeString( mw.ustring.sub( str, 1, 999 ) ) .. '.-' .. Excerpt.escapeString( mw.ustring.sub( str, -999 ) ) end return text:gsub( pattern, '' ) end -- Helper method to convert a comma-separated list of numbers or min-max ranges into a list of booleans -- @param filter Required. Comma-separated list of numbers or min-max ranges, for example '1,3-5' -- @return Map from integers to booleans, for example {1=true,2=false,3=true,4=true,5=true} -- @return Boolean indicating whether the filters should be treated as a blacklist or not -- @note Merging this into matchFilter is possible, but way too inefficient function Excerpt.parseFilter( filter ) local filters = {} local isBlacklist = false if string.sub( filter, 1, 1 ) == '-' then isBlacklist = true filter = string.sub( filter, 2 ) end local values = mw.text.split( filter, ',' ) -- split values: '1,3-5' to {'1','3-5'} for _, value in pairs( values ) do value = mw.text.trim( value ) local min, max = mw.ustring.match( value, '^(%d+)%s*[-–—]%s*(%d+)$' ) -- '3-5' to min=3 max=5 if not max then min, max = string.match( value, '^((%d+))$' ) end -- '1' to min=1 max=1 if max then for i = min, max do filters[ i ] = true end else filters[ value ] = true -- if we reach this point, the string had the form 'a,b,c' rather than '1,2,3' end end filter = { cache = {}, terms = filters } return filter, isBlacklist end -- Helper function to see if a value matches any of the given filters function Excerpt.matchFilter( value, filter ) if value == nil then return false elseif type(value) == "number" then return filter.terms[value] else local cached = filter.cache[value] if cached ~= nil then return cached end local lang = mw.language.getContentLanguage() local lcvalue = lang:lcfirst(value) local ucvalue = lang:ucfirst(value) for term in pairs( filter.terms ) do if value == tostring(term) or type(term) == "string" and ( lcvalue == term or ucvalue == term or mw.ustring.match( value, term ) ) then filter.cache[value] = true return true end end filter.cache[value] = false end end return Excerpt 14inzvnv7781xy5025bzi28282zvfdw 797364 797363 2026-03-06T12:56:17Z en>Sophivorus 0 Add option to only get the filename of the first file, see [[Module talk:Excerpt#Feature request for file excerpts]] 797364 Scribunto text/plain -- Module:Excerpt implements the Excerpt template -- Documentation and master version: https://en.wikipedia.org/wiki/Module:Excerpt -- Authors: User:Sophivorus, User:Certes, User:Aidan9382 & others -- License: CC-BY-SA-3.0 local parser = require( 'Module:WikitextParser' ) local yesno = require( 'Module:Yesno' ) local ok, config = pcall( require, 'Module:Excerpt/config' ) if not ok then config = {} end local Excerpt = {} -- Main entry point for templates function Excerpt.main( frame ) -- Make sure the requested page exists and get the wikitext local page = Excerpt.getArg( 1 ) if not page or page == '{{{1}}}' then return Excerpt.getError( 'no-page' ) end local title = mw.title.new( page ) if not title then return Excerpt.getError( 'invalid-title', page ) end local fragment = title.fragment -- save for later if title.isRedirect then title = title.redirectTarget if fragment == "" then fragment = title.fragment -- page merge potential end end if not title.exists then return Excerpt.getError( 'page-not-found', page ) end page = title.prefixedText local wikitext = title:getContent() -- Get the template params and process them local params = { hat = yesno( Excerpt.getArg( 'hat', true ) ), this = Excerpt.getArg( 'this' ), only = Excerpt.getArg( 'only' ), files = Excerpt.getArg( 'files', Excerpt.getArg( 'file' ) ), lists = Excerpt.getArg( 'lists', Excerpt.getArg( 'list' ) ), tables = Excerpt.getArg( 'tables', Excerpt.getArg( 'table' ) ), templates = Excerpt.getArg( 'templates', Excerpt.getArg( 'template' ) ), paragraphs = Excerpt.getArg( 'paragraphs', Excerpt.getArg( 'paragraph' ) ), references = yesno( Excerpt.getArg( 'references', true ) ), subsections = yesno( Excerpt.getArg( 'subsections', false ) ), links = yesno( Excerpt.getArg( 'links', true ) ), bold = yesno( Excerpt.getArg( 'bold', false ) ), briefDates = yesno( Excerpt.getArg( 'briefdates', false ) ), inline = yesno( Excerpt.getArg( 'inline' ) ), quote = yesno( Excerpt.getArg( 'quote' ) ), more = yesno( Excerpt.getArg( 'more' ) ), class = Excerpt.getArg( 'class' ), track = yesno( Excerpt.getArg( 'track', true ) ), displayTitle = Excerpt.getArg( 'displaytitle', page ), } -- Make sure the requested section exists and get the excerpt local excerpt local section = Excerpt.getArg( 2, fragment ) section = mw.text.trim( section ) if section == '' then section = nil end if section then excerpt = parser.getSectionTag( wikitext, section ) if not excerpt then if params.subsections then excerpt = parser.getSection( wikitext, section ) else local sections = parser.getSections( wikitext ) excerpt = sections[ section ] end end if not excerpt then return Excerpt.getError( 'section-not-found', section ) end if excerpt == '' then return Excerpt.getError( 'section-empty', section ) end else excerpt = parser.getLead( wikitext ) if excerpt == '' then return Excerpt.getError( 'lead-empty' ) end end -- Remove noinclude bits excerpt = excerpt:gsub( '<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>', '' ) -- Filter various elements from the excerpt excerpt = Excerpt.filterFiles( excerpt, params.files ) excerpt = Excerpt.filterLists( excerpt, params.lists ) excerpt = Excerpt.filterTables( excerpt, params.tables ) excerpt = Excerpt.filterParagraphs( excerpt, params.paragraphs ) -- If no file is found, try to get one from the infobox if ( params.only == 'file' or params.only == 'files' or params.only == 'filename' or not params.only and ( not params.files or params.files ~= '0' ) ) -- caller asked for files and not section -- and we're in the lead section and config.captions -- and we have the config option required to try finding files in infoboxes and #parser.getFiles( excerpt ) == 0 -- and there're no files in the excerpt then excerpt = Excerpt.addInfoboxFile( excerpt ) end -- Filter the templates by appending the templates blacklist to the templates filter if config.blacklist then local blacklist = table.concat( config.blacklist, ',' ) if params.templates then if string.sub( params.templates, 1, 1 ) == '-' then params.templates = params.templates .. ',' .. blacklist end else params.templates = '-' .. blacklist end end excerpt = Excerpt.filterTemplates( excerpt, params.templates ) -- Leave only the requested elements if params.only == 'file' or params.only == 'files' then local files = parser.getFiles( excerpt ) excerpt = params.only == 'file' and files[1] or table.concat( files, '\n\n' ) end if params.only == 'list' or params.only == 'lists' then local lists = parser.getLists( excerpt ) excerpt = params.only == 'list' and lists[1] or table.concat( lists, '\n\n' ) end if params.only == 'table' or params.only == 'tables' then local tables = parser.getTables( excerpt ) excerpt = params.only == 'table' and tables[1] or table.concat( tables, '\n\n' ) end if params.only == 'paragraph' or params.only == 'paragraphs' then local paragraphs = parser.getParagraphs( excerpt ) excerpt = params.only == 'paragraph' and paragraphs[1] or table.concat( paragraphs, '\n\n' ) end if params.only == 'template' or params.only == 'templates' then local templates = parser.getTemplates( excerpt ) excerpt = params.only == 'template' and templates[1] or table.concat( templates, '\n\n' ) end if params.only == 'filename' then local files = parser.getFiles( excerpt ) local file = files[1] excerpt = file and parser.getFileName( file ) or 'Noimage.png' params.inline = true end -- @todo Make more robust and move downwards if params.briefDates then excerpt = Excerpt.fixDates( excerpt ) end -- Remove unwanted elements excerpt = Excerpt.removeComments( excerpt ) excerpt = Excerpt.removeSelfLinks( excerpt ) excerpt = Excerpt.removeNonFreeFiles( excerpt ) excerpt = Excerpt.removeBehaviorSwitches( excerpt ) -- Fix or remove the references if params.references then excerpt = Excerpt.fixReferences( excerpt, page, wikitext ) else excerpt = Excerpt.removeReferences( excerpt ) end -- Remove wikilinks if not params.links then excerpt = Excerpt.removeLinks( excerpt ) end -- Link the bold text near the start of most leads and then remove it if not section then excerpt = Excerpt.linkBold( excerpt, page ) end if not params.bold then excerpt = Excerpt.removeBold( excerpt ) end -- Remove extra line breaks but leave one before and after so the parser interprets lists, tables, etc. correctly excerpt = excerpt:gsub( '\n\n\n+', '\n\n' ) excerpt = mw.text.trim( excerpt ) excerpt = '\n' .. excerpt .. '\n' -- Remove nested categories excerpt = frame:preprocess( excerpt ) excerpt = Excerpt.removeCategories( excerpt ) -- Add tracking categories if params.track and config.categories then excerpt = Excerpt.addTrackingCategories( excerpt ) end -- Build the final output if params.inline then return mw.text.trim( excerpt ) end local tag = params.quote and 'blockquote' or 'div' local block = mw.html.create( tag ):addClass( 'excerpt-block' ):addClass( params.class ) if config.styles then local styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) block:node( styles ) end if params.hat then local hat = Excerpt.getHat( page, section, params ) block:node( hat ) end excerpt = mw.html.create( 'div' ):addClass( 'excerpt' ):wikitext( excerpt ) block:node( excerpt ) if params.more then local more = Excerpt.getReadMore( page, section ) block:node( more ) end return block end -- Filter the files in the given wikitext against the given filter function Excerpt.filterFiles( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local files = parser.getFiles( wikitext ) for index, file in pairs( files ) do local name = parser.getFileName( file ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( name, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( name, filters ) ) then wikitext = Excerpt.removeString( wikitext, file ) end end return wikitext end -- Filter the lists in the given wikitext against the given filter function Excerpt.filterLists( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local lists = parser.getLists( wikitext ) for index, list in pairs( lists ) do if isBlacklist and Excerpt.matchFilter( index, filters ) or not isBlacklist and not Excerpt.matchFilter( index, filters ) then wikitext = Excerpt.removeString( wikitext, list ) end end return wikitext end -- Filter the tables in the given wikitext against the given filter function Excerpt.filterTables( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local tables = parser.getTables( wikitext ) for index, tableWikitext in pairs( tables ) do local id = parser.getTableAttribute( tableWikitext, 'id' ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( id, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( id, filters ) ) then wikitext = Excerpt.removeString( wikitext, tableWikitext ) end end return wikitext end -- Filter the paragraphs in the given wikitext against the given filter function Excerpt.filterParagraphs( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local paragraphs = parser.getParagraphs( wikitext ) for index, paragraph in pairs( paragraphs ) do if isBlacklist and Excerpt.matchFilter( index, filters ) or not isBlacklist and not Excerpt.matchFilter( index, filters ) then wikitext = Excerpt.removeString( wikitext, paragraph ) end end return wikitext end -- Filter the templates in the given wikitext against the given filter function Excerpt.filterTemplates( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local templates = parser.getTemplates( wikitext ) for index, template in pairs( templates ) do local name = parser.getTemplateName( template ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( name, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( name, filters ) ) then wikitext = Excerpt.removeString( wikitext, template ) end end return wikitext end function Excerpt.addInfoboxFile( excerpt ) -- We cannot distinguish the infobox from the other templates, so we search them all local templates = parser.getTemplates( excerpt ) for _, template in pairs( templates ) do local parameters = parser.getTemplateParameters( template ) local file, captions, caption, cssClasses, cssClass for _, pair in pairs( config.captions ) do file = pair[1] file = parameters[file] if file and Excerpt.matchAny( file, '^.*%.', { '[Jj][Pp][Ee]?[Gg]', '[Pp][Nn][Gg]', '[Gg][Ii][Ff]', '[Ss][Vv][Gg]' }, '.*' ) then file = string.match( file, '%[?%[?.-:([^{|]+)%]?%]?' ) or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs( captions ) do if parameters[ p ] then caption = parameters[ p ] break end end -- Check for CSS classes -- We opt to use skin-invert-image instead of skin-invert -- in all other cases, the CSS provided in the infobox is used if pair[3] then cssClasses = pair[3] for _, p in pairs( cssClasses ) do if parameters[ p ] then cssClass = ( parameters[ p ] == 'skin-invert' ) and 'skin-invert-image' or parameters[ p ] break end end end local class = cssClass and ( '|class=' .. cssClass ) or '' return '[[File:' .. file .. class .. '|thumb|' .. ( caption or '' ) .. ']]\n' .. excerpt end end end return excerpt end function Excerpt.removeNonFreeFiles( wikitext ) local files = parser.getFiles( wikitext ) for _, file in pairs( files ) do local fileName = 'File:' .. parser.getFileName( file ) local fileTitle = mw.title.new( fileName ) if fileTitle then local fileDescription = fileTitle:getContent() if not fileDescription or fileDescription == '' then local frame = mw.getCurrentFrame() fileDescription = frame:preprocess( '{{' .. fileName .. '}}' ) -- try Commons end if fileDescription and string.match( fileDescription, '[Nn]on%-free' ) then wikitext = Excerpt.removeString( wikitext, file ) end end end return wikitext end function Excerpt.getHat( page, section, params ) local hat -- Build the text if params.this then hat = params.this elseif params.quote then hat = Excerpt.getMessage( 'this' ) elseif params.only then hat = Excerpt.getMessage( params.only ) else hat = Excerpt.getMessage( 'section' ) end hat = hat .. ' ' .. Excerpt.getMessage( 'excerpt' ) -- Build the link if section then hat = hat .. ' [[:' .. page .. '#' .. mw.uri.anchorEncode( section ) .. '|' .. params.displayTitle .. ' § ' .. section:gsub( '%[%[([^]|]+)|?[^]]*%]%]', '%1' ) .. ']].' -- remove nested links else hat = hat .. ' [[:' .. page .. '|' .. params.displayTitle .. ']].' end -- Build the edit link local title = mw.title.new( page ) local editUrl = title:fullUrl( 'action=edit' ) hat = hat .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. editUrl .. ' ' .. mw.message.new( 'editsection' ):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' if config.hat then local frame = mw.getCurrentFrame() hat = config.hat .. hat .. '}}' hat = frame:preprocess( hat ) else hat = mw.html.create( 'div' ):addClass( 'dablink excerpt-hat' ):wikitext( hat ) end return hat end function Excerpt.getReadMore( page, section ) local link = "'''[[" .. page if section then link = link .. '#' .. section end local text = Excerpt.getMessage( 'more' ) link = link .. '|' .. text .. "]]'''" link = mw.html.create( 'div' ):addClass( 'noprint excerpt-more' ):wikitext( link ) return link end -- Fix birth and death dates, but only in the first paragraph -- @todo Use parser.getParagraphs() to get the first paragraph function Excerpt.fixDates( excerpt ) local start local s local e = 0 repeat start = e + 1 s, e = mw.ustring.find( excerpt, '%s*%b{}%s*', start ) until not s or s > start s, e = mw.ustring.find( excerpt, '%b()', start ) -- get (...), which may be (year–year) if s and s < start + 100 then -- look only near the start local excerptStart = mw.ustring.sub( excerpt, s, e ) local year1, conjunction, year2 = string.match( excerptStart, '(%d%d%d+)(.-)(%d%d%d+)' ) if year1 and year2 and ( string.match( conjunction, '[%-–—]' ) or string.match( conjunction, '{{%s*[sS]nd%s*}}' ) ) then local y1 = tonumber( year1 ) local y2 = tonumber( year2 ) if y2 > y1 and y2 < y1 + 125 and y1 <= tonumber( os.date( '%Y' ) ) then excerpt = mw.ustring.sub( excerpt, 1, s ) .. year1 .. '–' .. year2 .. mw.ustring.sub( excerpt, e ) end end end return excerpt end -- Replace the first call to each reference defined outside of the excerpt for the full reference, to prevent undefined references -- Then prefix the page title to the reference names to prevent conflicts -- that is, replace <ref name="Foo"> for <ref name="Title of the article Foo"> -- and also <ref name="Foo" /> for <ref name="Title of the article Foo" /> -- also remove reference groups: <ref name="Foo" group="Bar"> for <ref name="Title of the article Foo"> -- and <ref group="Bar"> for <ref> -- @todo The current regex may fail in cases with both kinds of quotes, like <ref name="Darwin's book"> function Excerpt.fixReferences( excerpt, page, wikitext ) local references = parser.getReferences( excerpt ) local fixed = {} for _, reference in pairs( references ) do local name = parser.getTagAttribute( reference, 'name' ) if not fixed[ name ] then -- fix each reference only once local content = parser.getTagContent( reference ) if not content then -- reference is self-closing local full = parser.getReference( excerpt, name ) if not full then -- the reference is not defined in the excerpt full = parser.getReference( wikitext, name ) if full then excerpt = excerpt:gsub( Excerpt.escapeString( reference ), Excerpt.escapeString( full ), 1 ) end table.insert( fixed, name ) end end end end -- Prepend the page title to the reference names to prevent conflicts with other references in the transcluding page excerpt = excerpt:gsub( '< *[Rr][Ee][Ff][^>]*name *= *["\']?([^"\'>/]+)["\']?[^>/]*(/?) *>', '<ref name="' .. page:gsub( '"', '' ) .. ' %1"%2>' ) -- Remove reference groups because they don't apply to the transcluding page excerpt = excerpt:gsub( '< *[Rr][Ee][Ff] *group *= *["\']?[^"\'>/]+["\'] *>', '<ref>' ) return excerpt end function Excerpt.removeReferences( excerpt ) local references = parser.getReferences( excerpt ) for _, reference in pairs( references ) do excerpt = Excerpt.removeString( excerpt, reference ) end return excerpt end function Excerpt.removeCategories( excerpt ) local categories = parser.getCategories( excerpt ) for _, category in pairs( categories ) do excerpt = Excerpt.removeString( excerpt, category ) end return excerpt end function Excerpt.removeBehaviorSwitches( excerpt ) return excerpt:gsub( '__[A-Z]+__', '' ) end function Excerpt.removeComments( excerpt ) return excerpt:gsub( '<!%-%-.-%-%->', '' ) end function Excerpt.removeBold( excerpt ) return excerpt:gsub( "'''", '' ) end function Excerpt.removeLinks( excerpt ) local links = parser.getLinks( excerpt ) for _, link in pairs( links ) do excerpt = Excerpt.removeString( excerpt, link ) end return excerpt end -- @todo Use parser.getLinks function Excerpt.removeSelfLinks( excerpt ) local lang = mw.language.getContentLanguage() local page = Excerpt.escapeString( mw.title.getCurrentTitle().prefixedText ) local ucpage = lang:ucfirst( page ) local lcpage = lang:lcfirst( page ) excerpt = excerpt :gsub( '%[%[(' .. ucpage .. ')%]%]', '%1' ) :gsub( '%[%[(' .. lcpage .. ')%]%]', '%1' ) :gsub( '%[%[' .. ucpage .. '|([^]]+)%]%]', '%1' ) :gsub( '%[%[' .. lcpage .. '|([^]]+)%]%]', '%1' ) return excerpt end -- Replace the bold title or synonym near the start of the page by a link to the page function Excerpt.linkBold( excerpt, page ) local lang = mw.language.getContentLanguage() local position = mw.ustring.find( excerpt, "'''" .. lang:ucfirst( page ) .. "'''", 1, true ) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find( excerpt, "'''" .. lang:lcfirst( page ) .. "'''", 1, true ) -- plain search: special characters in page represent themselves if position then local length = mw.ustring.len( page ) excerpt = mw.ustring.sub( excerpt, 1, position + 2 ) .. '[[' .. mw.ustring.sub( excerpt, position + 3, position + length + 2 ) .. ']]' .. mw.ustring.sub( excerpt, position + length + 3, -1 ) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) excerpt = mw.ustring.gsub( excerpt, "'''(.-'*)'''", function ( text ) if not string.find( text, '%[' ) and not string.find( text, '%{' ) then -- if not wikilinked or some weird template return "'''[[" .. page .. '|' .. text .. "]]'''" -- replace '''Foo''' by '''[[page|Foo]]''' else return nil -- instruct gsub to make no change end end, 1 ) -- terminates the anonymous replacement function passed to gsub end return excerpt end function Excerpt.addTrackingCategories( excerpt ) local currentTitle = mw.title.getCurrentTitle() local addedCategories = false local contentCategory = config.categories.content if contentCategory and currentTitle.isContentPage then addedCategories = true excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ currentTitle.namespace ] if namespaceCategory then addedCategories = true excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end if addedCategories then excerpt = excerpt .. '\n' end return excerpt end -- Helper method to match from a list of regular expressions -- Like so: match pre..list[1]..post or pre..list[2]..post or ... function Excerpt.matchAny( text, pre, list, post, init ) for i = 1, #list do local match = { mw.ustring.match( text, pre .. list[ i ] .. post, init ) } if match[1] then return unpack( match ) end end return nil end -- Helper function to get arguments -- args from Lua calls have priority over parent args from template function Excerpt.getArg( key, default ) local frame = mw.getCurrentFrame() for k, value in pairs( frame:getParent().args ) do if k == key and mw.text.trim( value ) ~= '' then return value end end for k, value in pairs( frame.args ) do if k == key and mw.text.trim( value ) ~= '' then return value end end return default end -- Helper method to get an error message -- This method also categorizes the current page in one of the configured error categories function Excerpt.getError( key, value ) local message = Excerpt.getMessage( 'error-' .. key, value ) local markup = mw.html.create( 'div' ):addClass( 'error' ):wikitext( message ) if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then markup:node( '[[Category:' .. config.categories.errors .. ']]' ) end return markup end -- Helper method to get a localized message -- This method uses Module:TNT to get localized messages from https://commons.wikimedia.org/wiki/Data:I18n/Module:Excerpt.tab -- If Module:TNT is not available or the localized message does not exist, the key is returned instead function Excerpt.getMessage( key, value ) local ok2, TNT = pcall( require, 'Module:TNT' ) if not ok2 then return key end local ok3, message = pcall( TNT.format, 'I18n/Module:Excerpt.tab', key, value ) if not ok3 then return key end return message end -- Helper method to escape a string for use in regexes function Excerpt.escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Helper method to remove a string from a text -- @param text Text from where to remove the string -- @param str String to remove -- @return The given text with the string removed function Excerpt.removeString( text, str ) local pattern = Excerpt.escapeString( str ) if #pattern > 9999 then -- strings longer than 10000 bytes can't be put into regexes pattern = Excerpt.escapeString( mw.ustring.sub( str, 1, 999 ) ) .. '.-' .. Excerpt.escapeString( mw.ustring.sub( str, -999 ) ) end return text:gsub( pattern, '' ) end -- Helper method to convert a comma-separated list of numbers or min-max ranges into a list of booleans -- @param filter Required. Comma-separated list of numbers or min-max ranges, for example '1,3-5' -- @return Map from integers to booleans, for example {1=true,2=false,3=true,4=true,5=true} -- @return Boolean indicating whether the filters should be treated as a blacklist or not -- @note Merging this into matchFilter is possible, but way too inefficient function Excerpt.parseFilter( filter ) local filters = {} local isBlacklist = false if string.sub( filter, 1, 1 ) == '-' then isBlacklist = true filter = string.sub( filter, 2 ) end local values = mw.text.split( filter, ',' ) -- split values: '1,3-5' to {'1','3-5'} for _, value in pairs( values ) do value = mw.text.trim( value ) local min, max = mw.ustring.match( value, '^(%d+)%s*[-–—]%s*(%d+)$' ) -- '3-5' to min=3 max=5 if not max then min, max = string.match( value, '^((%d+))$' ) end -- '1' to min=1 max=1 if max then for i = min, max do filters[ i ] = true end else filters[ value ] = true -- if we reach this point, the string had the form 'a,b,c' rather than '1,2,3' end end filter = { cache = {}, terms = filters } return filter, isBlacklist end -- Helper function to see if a value matches any of the given filters function Excerpt.matchFilter( value, filter ) if value == nil then return false elseif type(value) == "number" then return filter.terms[value] else local cached = filter.cache[value] if cached ~= nil then return cached end local lang = mw.language.getContentLanguage() local lcvalue = lang:lcfirst(value) local ucvalue = lang:ucfirst(value) for term in pairs( filter.terms ) do if value == tostring(term) or type(term) == "string" and ( lcvalue == term or ucvalue == term or mw.ustring.match( value, term ) ) then filter.cache[value] = true return true end end filter.cache[value] = false end end return Excerpt f13qcb3mtjeccbzr0i5jmt33ddc570f 797365 797364 2026-06-08T20:26:19Z SM7 3953 197 revisions imported from [[:en:Module:Excerpt]] 797364 Scribunto text/plain -- Module:Excerpt implements the Excerpt template -- Documentation and master version: https://en.wikipedia.org/wiki/Module:Excerpt -- Authors: User:Sophivorus, User:Certes, User:Aidan9382 & others -- License: CC-BY-SA-3.0 local parser = require( 'Module:WikitextParser' ) local yesno = require( 'Module:Yesno' ) local ok, config = pcall( require, 'Module:Excerpt/config' ) if not ok then config = {} end local Excerpt = {} -- Main entry point for templates function Excerpt.main( frame ) -- Make sure the requested page exists and get the wikitext local page = Excerpt.getArg( 1 ) if not page or page == '{{{1}}}' then return Excerpt.getError( 'no-page' ) end local title = mw.title.new( page ) if not title then return Excerpt.getError( 'invalid-title', page ) end local fragment = title.fragment -- save for later if title.isRedirect then title = title.redirectTarget if fragment == "" then fragment = title.fragment -- page merge potential end end if not title.exists then return Excerpt.getError( 'page-not-found', page ) end page = title.prefixedText local wikitext = title:getContent() -- Get the template params and process them local params = { hat = yesno( Excerpt.getArg( 'hat', true ) ), this = Excerpt.getArg( 'this' ), only = Excerpt.getArg( 'only' ), files = Excerpt.getArg( 'files', Excerpt.getArg( 'file' ) ), lists = Excerpt.getArg( 'lists', Excerpt.getArg( 'list' ) ), tables = Excerpt.getArg( 'tables', Excerpt.getArg( 'table' ) ), templates = Excerpt.getArg( 'templates', Excerpt.getArg( 'template' ) ), paragraphs = Excerpt.getArg( 'paragraphs', Excerpt.getArg( 'paragraph' ) ), references = yesno( Excerpt.getArg( 'references', true ) ), subsections = yesno( Excerpt.getArg( 'subsections', false ) ), links = yesno( Excerpt.getArg( 'links', true ) ), bold = yesno( Excerpt.getArg( 'bold', false ) ), briefDates = yesno( Excerpt.getArg( 'briefdates', false ) ), inline = yesno( Excerpt.getArg( 'inline' ) ), quote = yesno( Excerpt.getArg( 'quote' ) ), more = yesno( Excerpt.getArg( 'more' ) ), class = Excerpt.getArg( 'class' ), track = yesno( Excerpt.getArg( 'track', true ) ), displayTitle = Excerpt.getArg( 'displaytitle', page ), } -- Make sure the requested section exists and get the excerpt local excerpt local section = Excerpt.getArg( 2, fragment ) section = mw.text.trim( section ) if section == '' then section = nil end if section then excerpt = parser.getSectionTag( wikitext, section ) if not excerpt then if params.subsections then excerpt = parser.getSection( wikitext, section ) else local sections = parser.getSections( wikitext ) excerpt = sections[ section ] end end if not excerpt then return Excerpt.getError( 'section-not-found', section ) end if excerpt == '' then return Excerpt.getError( 'section-empty', section ) end else excerpt = parser.getLead( wikitext ) if excerpt == '' then return Excerpt.getError( 'lead-empty' ) end end -- Remove noinclude bits excerpt = excerpt:gsub( '<[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>.-</[Nn][Oo][Ii][Nn][Cc][Ll][Uu][Dd][Ee]>', '' ) -- Filter various elements from the excerpt excerpt = Excerpt.filterFiles( excerpt, params.files ) excerpt = Excerpt.filterLists( excerpt, params.lists ) excerpt = Excerpt.filterTables( excerpt, params.tables ) excerpt = Excerpt.filterParagraphs( excerpt, params.paragraphs ) -- If no file is found, try to get one from the infobox if ( params.only == 'file' or params.only == 'files' or params.only == 'filename' or not params.only and ( not params.files or params.files ~= '0' ) ) -- caller asked for files and not section -- and we're in the lead section and config.captions -- and we have the config option required to try finding files in infoboxes and #parser.getFiles( excerpt ) == 0 -- and there're no files in the excerpt then excerpt = Excerpt.addInfoboxFile( excerpt ) end -- Filter the templates by appending the templates blacklist to the templates filter if config.blacklist then local blacklist = table.concat( config.blacklist, ',' ) if params.templates then if string.sub( params.templates, 1, 1 ) == '-' then params.templates = params.templates .. ',' .. blacklist end else params.templates = '-' .. blacklist end end excerpt = Excerpt.filterTemplates( excerpt, params.templates ) -- Leave only the requested elements if params.only == 'file' or params.only == 'files' then local files = parser.getFiles( excerpt ) excerpt = params.only == 'file' and files[1] or table.concat( files, '\n\n' ) end if params.only == 'list' or params.only == 'lists' then local lists = parser.getLists( excerpt ) excerpt = params.only == 'list' and lists[1] or table.concat( lists, '\n\n' ) end if params.only == 'table' or params.only == 'tables' then local tables = parser.getTables( excerpt ) excerpt = params.only == 'table' and tables[1] or table.concat( tables, '\n\n' ) end if params.only == 'paragraph' or params.only == 'paragraphs' then local paragraphs = parser.getParagraphs( excerpt ) excerpt = params.only == 'paragraph' and paragraphs[1] or table.concat( paragraphs, '\n\n' ) end if params.only == 'template' or params.only == 'templates' then local templates = parser.getTemplates( excerpt ) excerpt = params.only == 'template' and templates[1] or table.concat( templates, '\n\n' ) end if params.only == 'filename' then local files = parser.getFiles( excerpt ) local file = files[1] excerpt = file and parser.getFileName( file ) or 'Noimage.png' params.inline = true end -- @todo Make more robust and move downwards if params.briefDates then excerpt = Excerpt.fixDates( excerpt ) end -- Remove unwanted elements excerpt = Excerpt.removeComments( excerpt ) excerpt = Excerpt.removeSelfLinks( excerpt ) excerpt = Excerpt.removeNonFreeFiles( excerpt ) excerpt = Excerpt.removeBehaviorSwitches( excerpt ) -- Fix or remove the references if params.references then excerpt = Excerpt.fixReferences( excerpt, page, wikitext ) else excerpt = Excerpt.removeReferences( excerpt ) end -- Remove wikilinks if not params.links then excerpt = Excerpt.removeLinks( excerpt ) end -- Link the bold text near the start of most leads and then remove it if not section then excerpt = Excerpt.linkBold( excerpt, page ) end if not params.bold then excerpt = Excerpt.removeBold( excerpt ) end -- Remove extra line breaks but leave one before and after so the parser interprets lists, tables, etc. correctly excerpt = excerpt:gsub( '\n\n\n+', '\n\n' ) excerpt = mw.text.trim( excerpt ) excerpt = '\n' .. excerpt .. '\n' -- Remove nested categories excerpt = frame:preprocess( excerpt ) excerpt = Excerpt.removeCategories( excerpt ) -- Add tracking categories if params.track and config.categories then excerpt = Excerpt.addTrackingCategories( excerpt ) end -- Build the final output if params.inline then return mw.text.trim( excerpt ) end local tag = params.quote and 'blockquote' or 'div' local block = mw.html.create( tag ):addClass( 'excerpt-block' ):addClass( params.class ) if config.styles then local styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } ) block:node( styles ) end if params.hat then local hat = Excerpt.getHat( page, section, params ) block:node( hat ) end excerpt = mw.html.create( 'div' ):addClass( 'excerpt' ):wikitext( excerpt ) block:node( excerpt ) if params.more then local more = Excerpt.getReadMore( page, section ) block:node( more ) end return block end -- Filter the files in the given wikitext against the given filter function Excerpt.filterFiles( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local files = parser.getFiles( wikitext ) for index, file in pairs( files ) do local name = parser.getFileName( file ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( name, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( name, filters ) ) then wikitext = Excerpt.removeString( wikitext, file ) end end return wikitext end -- Filter the lists in the given wikitext against the given filter function Excerpt.filterLists( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local lists = parser.getLists( wikitext ) for index, list in pairs( lists ) do if isBlacklist and Excerpt.matchFilter( index, filters ) or not isBlacklist and not Excerpt.matchFilter( index, filters ) then wikitext = Excerpt.removeString( wikitext, list ) end end return wikitext end -- Filter the tables in the given wikitext against the given filter function Excerpt.filterTables( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local tables = parser.getTables( wikitext ) for index, tableWikitext in pairs( tables ) do local id = parser.getTableAttribute( tableWikitext, 'id' ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( id, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( id, filters ) ) then wikitext = Excerpt.removeString( wikitext, tableWikitext ) end end return wikitext end -- Filter the paragraphs in the given wikitext against the given filter function Excerpt.filterParagraphs( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local paragraphs = parser.getParagraphs( wikitext ) for index, paragraph in pairs( paragraphs ) do if isBlacklist and Excerpt.matchFilter( index, filters ) or not isBlacklist and not Excerpt.matchFilter( index, filters ) then wikitext = Excerpt.removeString( wikitext, paragraph ) end end return wikitext end -- Filter the templates in the given wikitext against the given filter function Excerpt.filterTemplates( wikitext, filter ) if not filter then return wikitext end local filters, isBlacklist = Excerpt.parseFilter( filter ) local templates = parser.getTemplates( wikitext ) for index, template in pairs( templates ) do local name = parser.getTemplateName( template ) if isBlacklist and ( Excerpt.matchFilter( index, filters ) or Excerpt.matchFilter( name, filters ) ) or not isBlacklist and ( not Excerpt.matchFilter( index, filters ) and not Excerpt.matchFilter( name, filters ) ) then wikitext = Excerpt.removeString( wikitext, template ) end end return wikitext end function Excerpt.addInfoboxFile( excerpt ) -- We cannot distinguish the infobox from the other templates, so we search them all local templates = parser.getTemplates( excerpt ) for _, template in pairs( templates ) do local parameters = parser.getTemplateParameters( template ) local file, captions, caption, cssClasses, cssClass for _, pair in pairs( config.captions ) do file = pair[1] file = parameters[file] if file and Excerpt.matchAny( file, '^.*%.', { '[Jj][Pp][Ee]?[Gg]', '[Pp][Nn][Gg]', '[Gg][Ii][Ff]', '[Ss][Vv][Gg]' }, '.*' ) then file = string.match( file, '%[?%[?.-:([^{|]+)%]?%]?' ) or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg captions = pair[2] for _, p in pairs( captions ) do if parameters[ p ] then caption = parameters[ p ] break end end -- Check for CSS classes -- We opt to use skin-invert-image instead of skin-invert -- in all other cases, the CSS provided in the infobox is used if pair[3] then cssClasses = pair[3] for _, p in pairs( cssClasses ) do if parameters[ p ] then cssClass = ( parameters[ p ] == 'skin-invert' ) and 'skin-invert-image' or parameters[ p ] break end end end local class = cssClass and ( '|class=' .. cssClass ) or '' return '[[File:' .. file .. class .. '|thumb|' .. ( caption or '' ) .. ']]\n' .. excerpt end end end return excerpt end function Excerpt.removeNonFreeFiles( wikitext ) local files = parser.getFiles( wikitext ) for _, file in pairs( files ) do local fileName = 'File:' .. parser.getFileName( file ) local fileTitle = mw.title.new( fileName ) if fileTitle then local fileDescription = fileTitle:getContent() if not fileDescription or fileDescription == '' then local frame = mw.getCurrentFrame() fileDescription = frame:preprocess( '{{' .. fileName .. '}}' ) -- try Commons end if fileDescription and string.match( fileDescription, '[Nn]on%-free' ) then wikitext = Excerpt.removeString( wikitext, file ) end end end return wikitext end function Excerpt.getHat( page, section, params ) local hat -- Build the text if params.this then hat = params.this elseif params.quote then hat = Excerpt.getMessage( 'this' ) elseif params.only then hat = Excerpt.getMessage( params.only ) else hat = Excerpt.getMessage( 'section' ) end hat = hat .. ' ' .. Excerpt.getMessage( 'excerpt' ) -- Build the link if section then hat = hat .. ' [[:' .. page .. '#' .. mw.uri.anchorEncode( section ) .. '|' .. params.displayTitle .. ' § ' .. section:gsub( '%[%[([^]|]+)|?[^]]*%]%]', '%1' ) .. ']].' -- remove nested links else hat = hat .. ' [[:' .. page .. '|' .. params.displayTitle .. ']].' end -- Build the edit link local title = mw.title.new( page ) local editUrl = title:fullUrl( 'action=edit' ) hat = hat .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>[' hat = hat .. editUrl .. ' ' .. mw.message.new( 'editsection' ):plain() hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>' if config.hat then local frame = mw.getCurrentFrame() hat = config.hat .. hat .. '}}' hat = frame:preprocess( hat ) else hat = mw.html.create( 'div' ):addClass( 'dablink excerpt-hat' ):wikitext( hat ) end return hat end function Excerpt.getReadMore( page, section ) local link = "'''[[" .. page if section then link = link .. '#' .. section end local text = Excerpt.getMessage( 'more' ) link = link .. '|' .. text .. "]]'''" link = mw.html.create( 'div' ):addClass( 'noprint excerpt-more' ):wikitext( link ) return link end -- Fix birth and death dates, but only in the first paragraph -- @todo Use parser.getParagraphs() to get the first paragraph function Excerpt.fixDates( excerpt ) local start local s local e = 0 repeat start = e + 1 s, e = mw.ustring.find( excerpt, '%s*%b{}%s*', start ) until not s or s > start s, e = mw.ustring.find( excerpt, '%b()', start ) -- get (...), which may be (year–year) if s and s < start + 100 then -- look only near the start local excerptStart = mw.ustring.sub( excerpt, s, e ) local year1, conjunction, year2 = string.match( excerptStart, '(%d%d%d+)(.-)(%d%d%d+)' ) if year1 and year2 and ( string.match( conjunction, '[%-–—]' ) or string.match( conjunction, '{{%s*[sS]nd%s*}}' ) ) then local y1 = tonumber( year1 ) local y2 = tonumber( year2 ) if y2 > y1 and y2 < y1 + 125 and y1 <= tonumber( os.date( '%Y' ) ) then excerpt = mw.ustring.sub( excerpt, 1, s ) .. year1 .. '–' .. year2 .. mw.ustring.sub( excerpt, e ) end end end return excerpt end -- Replace the first call to each reference defined outside of the excerpt for the full reference, to prevent undefined references -- Then prefix the page title to the reference names to prevent conflicts -- that is, replace <ref name="Foo"> for <ref name="Title of the article Foo"> -- and also <ref name="Foo" /> for <ref name="Title of the article Foo" /> -- also remove reference groups: <ref name="Foo" group="Bar"> for <ref name="Title of the article Foo"> -- and <ref group="Bar"> for <ref> -- @todo The current regex may fail in cases with both kinds of quotes, like <ref name="Darwin's book"> function Excerpt.fixReferences( excerpt, page, wikitext ) local references = parser.getReferences( excerpt ) local fixed = {} for _, reference in pairs( references ) do local name = parser.getTagAttribute( reference, 'name' ) if not fixed[ name ] then -- fix each reference only once local content = parser.getTagContent( reference ) if not content then -- reference is self-closing local full = parser.getReference( excerpt, name ) if not full then -- the reference is not defined in the excerpt full = parser.getReference( wikitext, name ) if full then excerpt = excerpt:gsub( Excerpt.escapeString( reference ), Excerpt.escapeString( full ), 1 ) end table.insert( fixed, name ) end end end end -- Prepend the page title to the reference names to prevent conflicts with other references in the transcluding page excerpt = excerpt:gsub( '< *[Rr][Ee][Ff][^>]*name *= *["\']?([^"\'>/]+)["\']?[^>/]*(/?) *>', '<ref name="' .. page:gsub( '"', '' ) .. ' %1"%2>' ) -- Remove reference groups because they don't apply to the transcluding page excerpt = excerpt:gsub( '< *[Rr][Ee][Ff] *group *= *["\']?[^"\'>/]+["\'] *>', '<ref>' ) return excerpt end function Excerpt.removeReferences( excerpt ) local references = parser.getReferences( excerpt ) for _, reference in pairs( references ) do excerpt = Excerpt.removeString( excerpt, reference ) end return excerpt end function Excerpt.removeCategories( excerpt ) local categories = parser.getCategories( excerpt ) for _, category in pairs( categories ) do excerpt = Excerpt.removeString( excerpt, category ) end return excerpt end function Excerpt.removeBehaviorSwitches( excerpt ) return excerpt:gsub( '__[A-Z]+__', '' ) end function Excerpt.removeComments( excerpt ) return excerpt:gsub( '<!%-%-.-%-%->', '' ) end function Excerpt.removeBold( excerpt ) return excerpt:gsub( "'''", '' ) end function Excerpt.removeLinks( excerpt ) local links = parser.getLinks( excerpt ) for _, link in pairs( links ) do excerpt = Excerpt.removeString( excerpt, link ) end return excerpt end -- @todo Use parser.getLinks function Excerpt.removeSelfLinks( excerpt ) local lang = mw.language.getContentLanguage() local page = Excerpt.escapeString( mw.title.getCurrentTitle().prefixedText ) local ucpage = lang:ucfirst( page ) local lcpage = lang:lcfirst( page ) excerpt = excerpt :gsub( '%[%[(' .. ucpage .. ')%]%]', '%1' ) :gsub( '%[%[(' .. lcpage .. ')%]%]', '%1' ) :gsub( '%[%[' .. ucpage .. '|([^]]+)%]%]', '%1' ) :gsub( '%[%[' .. lcpage .. '|([^]]+)%]%]', '%1' ) return excerpt end -- Replace the bold title or synonym near the start of the page by a link to the page function Excerpt.linkBold( excerpt, page ) local lang = mw.language.getContentLanguage() local position = mw.ustring.find( excerpt, "'''" .. lang:ucfirst( page ) .. "'''", 1, true ) -- look for "'''Foo''' is..." (uc) or "A '''foo''' is..." (lc) or mw.ustring.find( excerpt, "'''" .. lang:lcfirst( page ) .. "'''", 1, true ) -- plain search: special characters in page represent themselves if position then local length = mw.ustring.len( page ) excerpt = mw.ustring.sub( excerpt, 1, position + 2 ) .. '[[' .. mw.ustring.sub( excerpt, position + 3, position + length + 2 ) .. ']]' .. mw.ustring.sub( excerpt, position + length + 3, -1 ) -- link it else -- look for anything unlinked in bold, assumed to be a synonym of the title (e.g. a person's birth name) excerpt = mw.ustring.gsub( excerpt, "'''(.-'*)'''", function ( text ) if not string.find( text, '%[' ) and not string.find( text, '%{' ) then -- if not wikilinked or some weird template return "'''[[" .. page .. '|' .. text .. "]]'''" -- replace '''Foo''' by '''[[page|Foo]]''' else return nil -- instruct gsub to make no change end end, 1 ) -- terminates the anonymous replacement function passed to gsub end return excerpt end function Excerpt.addTrackingCategories( excerpt ) local currentTitle = mw.title.getCurrentTitle() local addedCategories = false local contentCategory = config.categories.content if contentCategory and currentTitle.isContentPage then addedCategories = true excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]' end local namespaceCategory = config.categories[ currentTitle.namespace ] if namespaceCategory then addedCategories = true excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]' end if addedCategories then excerpt = excerpt .. '\n' end return excerpt end -- Helper method to match from a list of regular expressions -- Like so: match pre..list[1]..post or pre..list[2]..post or ... function Excerpt.matchAny( text, pre, list, post, init ) for i = 1, #list do local match = { mw.ustring.match( text, pre .. list[ i ] .. post, init ) } if match[1] then return unpack( match ) end end return nil end -- Helper function to get arguments -- args from Lua calls have priority over parent args from template function Excerpt.getArg( key, default ) local frame = mw.getCurrentFrame() for k, value in pairs( frame:getParent().args ) do if k == key and mw.text.trim( value ) ~= '' then return value end end for k, value in pairs( frame.args ) do if k == key and mw.text.trim( value ) ~= '' then return value end end return default end -- Helper method to get an error message -- This method also categorizes the current page in one of the configured error categories function Excerpt.getError( key, value ) local message = Excerpt.getMessage( 'error-' .. key, value ) local markup = mw.html.create( 'div' ):addClass( 'error' ):wikitext( message ) if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then markup:node( '[[Category:' .. config.categories.errors .. ']]' ) end return markup end -- Helper method to get a localized message -- This method uses Module:TNT to get localized messages from https://commons.wikimedia.org/wiki/Data:I18n/Module:Excerpt.tab -- If Module:TNT is not available or the localized message does not exist, the key is returned instead function Excerpt.getMessage( key, value ) local ok2, TNT = pcall( require, 'Module:TNT' ) if not ok2 then return key end local ok3, message = pcall( TNT.format, 'I18n/Module:Excerpt.tab', key, value ) if not ok3 then return key end return message end -- Helper method to escape a string for use in regexes function Excerpt.escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Helper method to remove a string from a text -- @param text Text from where to remove the string -- @param str String to remove -- @return The given text with the string removed function Excerpt.removeString( text, str ) local pattern = Excerpt.escapeString( str ) if #pattern > 9999 then -- strings longer than 10000 bytes can't be put into regexes pattern = Excerpt.escapeString( mw.ustring.sub( str, 1, 999 ) ) .. '.-' .. Excerpt.escapeString( mw.ustring.sub( str, -999 ) ) end return text:gsub( pattern, '' ) end -- Helper method to convert a comma-separated list of numbers or min-max ranges into a list of booleans -- @param filter Required. Comma-separated list of numbers or min-max ranges, for example '1,3-5' -- @return Map from integers to booleans, for example {1=true,2=false,3=true,4=true,5=true} -- @return Boolean indicating whether the filters should be treated as a blacklist or not -- @note Merging this into matchFilter is possible, but way too inefficient function Excerpt.parseFilter( filter ) local filters = {} local isBlacklist = false if string.sub( filter, 1, 1 ) == '-' then isBlacklist = true filter = string.sub( filter, 2 ) end local values = mw.text.split( filter, ',' ) -- split values: '1,3-5' to {'1','3-5'} for _, value in pairs( values ) do value = mw.text.trim( value ) local min, max = mw.ustring.match( value, '^(%d+)%s*[-–—]%s*(%d+)$' ) -- '3-5' to min=3 max=5 if not max then min, max = string.match( value, '^((%d+))$' ) end -- '1' to min=1 max=1 if max then for i = min, max do filters[ i ] = true end else filters[ value ] = true -- if we reach this point, the string had the form 'a,b,c' rather than '1,2,3' end end filter = { cache = {}, terms = filters } return filter, isBlacklist end -- Helper function to see if a value matches any of the given filters function Excerpt.matchFilter( value, filter ) if value == nil then return false elseif type(value) == "number" then return filter.terms[value] else local cached = filter.cache[value] if cached ~= nil then return cached end local lang = mw.language.getContentLanguage() local lcvalue = lang:lcfirst(value) local ucvalue = lang:ucfirst(value) for term in pairs( filter.terms ) do if value == tostring(term) or type(term) == "string" and ( lcvalue == term or ucvalue == term or mw.ustring.match( value, term ) ) then filter.cache[value] = true return true end end filter.cache[value] = false end end return Excerpt f13qcb3mtjeccbzr0i5jmt33ddc570f Module:WikitextParser 828 101021 797366 2024-02-22T14:26:25Z en>Sophivorus 0 [[WP:AES|←]]Created page with '-- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes & others -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get the requested tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param selector Tags to return, for example 'div' or 'div,span,gallery'. Omit to return all tags. -- @retu...' 797366 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes & others -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get the requested tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param selector Tags to return, for example 'div' or 'div,span,gallery'. Omit to return all tags. -- @return Sequence of strings containing the wikitext of the requested tags. -- @return Original wikitext minus requested tags. function WikitextParser.getTags( wikitext, selector ) local tags = {} local tagName, tagText, tagEnd local original = wikitext local count = 0 for tagStart, tagOpen in string.gmatch( original, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" /> or <br/> or <hr> if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tagText = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len(tagOpen) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( original, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( original, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tagText = string.sub( original, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( original, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tagText = string.sub( original, tagStart, tagEnd ) end count = count + 1 if isSelected( selector, count, tagName ) then table.insert( tags, tagText ) else wikitext = removeString( wikitext, tagText ) end end return tags, wikitext end -- Helper function to determine if a given element is filtered or not by a selector -- @todo Should probably merge with parseSelector local function isSelected( selector, count, value ) local map, blacklist = parseSelector( selector ) if not blacklist and ( not map or map[ count ] or map[ value ] ) or blacklist and map and not map[ count ] and not map[ value ] then return true end end -- Helper function to convert a comma-separated list of numbers or min-max ranges into a list of booleans -- @param selector Comma-separated list of numbers or min-max ranges, for example '1,3-5' -- @return Map from integers to booleans, for example {1=true,2=false,3=true,4=true,5=true} -- @return Boolean indicating whether the selector should be treated as a blacklist or not local function parseSelector( selector ) local map = {} local blacklist = false if not selector then return nil, false end if type( selector ) == 'number' then if selector < 0 then selector = -selector blacklist = true end map = { [ selector ] = true } elseif type( selector ) == 'string' then if string.sub( selector, 1, 1 ) == '-' then selector = string.sub( selector, 2 ) blacklist = true end local ranges = mw.text.split( value, ',' ) -- split ranges: '1,3-5' to {'1','3-5'} for _, range in pairs( ranges ) do range = mw.text.trim( range ) local min, max = mw.ustring.match( range, '^(%d+)%s*[-–—]%s*(%d+)$' ) -- '3-5' to min=3 max=5 if not max then min, max = string.match( range, '^((%d+))$' ) end -- '1' to min=1 max=1 if max then for i = min, max do map[ i ] = true end else map[ range ] = true -- if we reach this point, the string had the form 'a,b,c' rather than '1,2,3' end end -- List has the form { [1] = false, [2] = true, ['c'] = false } -- Convert it to { [1] = true, [2] = true, ['c'] = true } -- But if ANY value is set to false, treat the list as a blacklist elseif type( selector ) == 'table' then for i, v in pairs( selector ) do if v == false then blacklist = true end map[ i ] = true end end return map, blacklist end -- Helper function to remove a string from a text local function removeString( text, str ) local pattern = escapeString( str ) if #pattern > 9999 then -- strings longer than 10000 bytes can't be put into regexes pattern = escapeString( mw.ustring.sub( str, 1, 999 ) ) .. '.-' .. escapeString( mw.ustring.sub( str, -999 ) ) end return string.gsub( text, pattern, '' ) end -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end return WikitextParser 8k8815oycamkzykzm6p8k3wy86olvap 797367 797366 2024-02-22T14:33:41Z en>Sophivorus 0 797367 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes & others -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Helper function to remove a string from a text local function removeString( text, str ) local pattern = escapeString( str ) if #pattern > 9999 then -- strings longer than 10000 bytes can't be put into regexes pattern = escapeString( mw.ustring.sub( str, 1, 999 ) ) .. '.-' .. escapeString( mw.ustring.sub( str, -999 ) ) end return string.gsub( text, pattern, '' ) end -- Helper function to convert a comma-separated list of numbers or min-max ranges into a list of booleans -- @param selector Comma-separated list of numbers or min-max ranges, for example '1,3-5' -- @return Map from integers to booleans, for example {1=true,2=false,3=true,4=true,5=true} -- @return Boolean indicating whether the selector should be treated as a blacklist or not local function parseSelector( selector ) local map = {} local blacklist = false if not selector then return nil, false end if type( selector ) == 'number' then if selector < 0 then selector = -selector blacklist = true end map = { [ selector ] = true } elseif type( selector ) == 'string' then if string.sub( selector, 1, 1 ) == '-' then selector = string.sub( selector, 2 ) blacklist = true end local ranges = mw.text.split( value, ',' ) -- split ranges: '1,3-5' to {'1','3-5'} for _, range in pairs( ranges ) do range = mw.text.trim( range ) local min, max = mw.ustring.match( range, '^(%d+)%s*[-–—]%s*(%d+)$' ) -- '3-5' to min=3 max=5 if not max then min, max = string.match( range, '^((%d+))$' ) end -- '1' to min=1 max=1 if max then for i = min, max do map[ i ] = true end else map[ range ] = true -- if we reach this point, the string had the form 'a,b,c' rather than '1,2,3' end end -- List has the form { [1] = false, [2] = true, ['c'] = false } -- Convert it to { [1] = true, [2] = true, ['c'] = true } -- But if ANY value is set to false, treat the list as a blacklist elseif type( selector ) == 'table' then for i, v in pairs( selector ) do if v == false then blacklist = true end map[ i ] = true end end return map, blacklist end -- Helper function to determine if a given element is filtered or not by a selector -- @todo Should probably merge with parseSelector local function isSelected( selector, count, value ) local map, blacklist = parseSelector( selector ) if not blacklist and ( not map or map[ count ] or map[ value ] ) or blacklist and map and not map[ count ] and not map[ value ] then return true end end -- Get the requested tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param selector Tags to return, for example 'div' or 'div,span,gallery'. Omit to return all tags. -- @return Sequence of strings containing the wikitext of the requested tags. -- @return Original wikitext minus requested tags. function WikitextParser.getTags( wikitext, selector ) local tags = {} local tagName, tagText, tagEnd local original = wikitext local count = 0 for tagStart, tagOpen in string.gmatch( original, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" /> or <br/> or <hr> if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tagText = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len(tagOpen) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( original, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( original, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tagText = string.sub( original, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( original, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tagText = string.sub( original, tagStart, tagEnd ) end count = count + 1 if isSelected( selector, count, tagName ) then table.insert( tags, tagText ) else wikitext = removeString( wikitext, tagText ) end end return tags, wikitext end return WikitextParser avhmzztnap0n6ji4ed1ml2wbiezx4zs 797368 797367 2024-02-22T14:35:14Z en>Sophivorus 0 Fix variable name 797368 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes & others -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Helper function to remove a string from a text local function removeString( text, str ) local pattern = escapeString( str ) if #pattern > 9999 then -- strings longer than 10000 bytes can't be put into regexes pattern = escapeString( mw.ustring.sub( str, 1, 999 ) ) .. '.-' .. escapeString( mw.ustring.sub( str, -999 ) ) end return string.gsub( text, pattern, '' ) end -- Helper function to convert a comma-separated list of numbers or min-max ranges into a list of booleans -- @param selector Comma-separated list of numbers or min-max ranges, for example '1,3-5' -- @return Map from integers to booleans, for example {1=true,2=false,3=true,4=true,5=true} -- @return Boolean indicating whether the selector should be treated as a blacklist or not local function parseSelector( selector ) local map = {} local blacklist = false if not selector then return nil, false end if type( selector ) == 'number' then if selector < 0 then selector = -selector blacklist = true end map = { [ selector ] = true } elseif type( selector ) == 'string' then if string.sub( selector, 1, 1 ) == '-' then selector = string.sub( selector, 2 ) blacklist = true end local ranges = mw.text.split( selector, ',' ) -- split ranges: '1,3-5' to {'1','3-5'} for _, range in pairs( ranges ) do range = mw.text.trim( range ) local min, max = mw.ustring.match( range, '^(%d+)%s*[-–—]%s*(%d+)$' ) -- '3-5' to min=3 max=5 if not max then min, max = string.match( range, '^((%d+))$' ) end -- '1' to min=1 max=1 if max then for i = min, max do map[ i ] = true end else map[ range ] = true -- if we reach this point, the string had the form 'a,b,c' rather than '1,2,3' end end -- List has the form { [1] = false, [2] = true, ['c'] = false } -- Convert it to { [1] = true, [2] = true, ['c'] = true } -- But if ANY value is set to false, treat the list as a blacklist elseif type( selector ) == 'table' then for i, v in pairs( selector ) do if v == false then blacklist = true end map[ i ] = true end end return map, blacklist end -- Helper function to determine if a given element is filtered or not by a selector -- @todo Should probably merge with parseSelector local function isSelected( selector, count, value ) local map, blacklist = parseSelector( selector ) if not blacklist and ( not map or map[ count ] or map[ value ] ) or blacklist and map and not map[ count ] and not map[ value ] then return true end end -- Get the requested tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param selector Tags to return, for example 'div' or 'div,span,gallery'. Omit to return all tags. -- @return Sequence of strings containing the wikitext of the requested tags. -- @return Original wikitext minus requested tags. function WikitextParser.getTags( wikitext, selector ) local tags = {} local tagName, tagText, tagEnd local original = wikitext local count = 0 for tagStart, tagOpen in string.gmatch( original, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" /> or <br/> or <hr> if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tagText = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len(tagOpen) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( original, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( original, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tagText = string.sub( original, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( original, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tagText = string.sub( original, tagStart, tagEnd ) end count = count + 1 if isSelected( selector, count, tagName ) then table.insert( tags, tagText ) else wikitext = removeString( wikitext, tagText ) end end return tags, wikitext end return WikitextParser c5swnkuv2ypsja4mqh5ue6suzwit23i 797369 797368 2024-02-22T14:48:34Z en>Sophivorus 0 Merge parseSelector and isSelected into inSelector and generalize 797369 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes & others -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Helper function to remove a string from a text local function removeString( text, str ) local pattern = escapeString( str ) if #pattern > 9999 then -- strings longer than 10000 bytes can't be put into regexes pattern = escapeString( mw.ustring.sub( str, 1, 999 ) ) .. '.-' .. escapeString( mw.ustring.sub( str, -999 ) ) end return string.gsub( text, pattern, '' ) end -- Helper function to determine if the given value is in the given selector or not -- @param selector Comma-separated list of values, for example 'div,span' or '1,3-5' -- @param value Value to test, for example 'div' or 1 -- @return boolean local function inSelector( selector, value ) local map = {} local blacklist = false if not selector then return nil, false end if type( selector ) == 'number' then if selector < 0 then selector = -selector blacklist = true end map = { [ selector ] = true } elseif type( selector ) == 'string' then if string.sub( selector, 1, 1 ) == '-' then selector = string.sub( selector, 2 ) blacklist = true end local ranges = mw.text.split( selector, ',' ) -- split ranges: '1,3-5' to {'1','3-5'} for _, range in pairs( ranges ) do range = mw.text.trim( range ) local min, max = mw.ustring.match( range, '^(%d+)%s*[-–—]%s*(%d+)$' ) -- '3-5' to min=3 max=5 if not max then min, max = string.match( range, '^((%d+))$' ) end -- '1' to min=1 max=1 if max then for i = min, max do map[ i ] = true end else map[ range ] = true -- if we reach this point, the string had the form 'a,b,c' rather than '1,2,3' end end -- List has the form { [1] = false, [2] = true, ['c'] = false } -- Convert it to { [1] = true, [2] = true, ['c'] = true } -- But if ANY value is set to false, treat the list as a blacklist elseif type( selector ) == 'table' then for i, v in pairs( selector ) do if v == false then blacklist = true end map[ i ] = true end end if not blacklist and ( not map or map[ value ] ) or blacklist and map and not map[ value ] then return true end end -- Get the requested tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param selector Tags to return, for example 'div' or 'div,span,gallery'. Omit to return all tags. -- @return Sequence of strings containing the wikitext of the requested tags. -- @return Original wikitext minus requested tags. function WikitextParser.getTags( wikitext, selector ) local tags = {} local tagName, tagText, tagEnd local original = wikitext local count = 0 for tagStart, tagOpen in string.gmatch( original, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" /> or <br/> or <hr> if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tagText = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len(tagOpen) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( original, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( original, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tagText = string.sub( original, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( original, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tagText = string.sub( original, tagStart, tagEnd ) end count = count + 1 if inSelector( selector, tagName ) then table.insert( tags, tagText ) else wikitext = removeString( wikitext, tagText ) end end return tags, wikitext end return WikitextParser 0jpqzjirvxhndot81bm2g7c4xhdygv0 797370 797369 2024-02-22T14:52:52Z en>Sophivorus 0 Merge parseSelector and isSelected into inSelector and generalize 797370 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes & others -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Helper function to remove a string from a text local function removeString( text, str ) local pattern = escapeString( str ) if #pattern > 9999 then -- strings longer than 10000 bytes can't be put into regexes pattern = escapeString( mw.ustring.sub( str, 1, 999 ) ) .. '.-' .. escapeString( mw.ustring.sub( str, -999 ) ) end return string.gsub( text, pattern, '' ) end -- Helper function to determine if the given value is in the given selector or not -- @param value Value to test, for example 'div' or 1 -- @param selector Comma-separated list of values, for example 'div,span' or '1,3-5' -- @return boolean local function inSelector( value, selector ) local map = {} local blacklist = false if not selector then return true end if type( selector ) == 'number' then if selector < 0 then selector = -selector blacklist = true end map = { [ selector ] = true } elseif type( selector ) == 'string' then if string.sub( selector, 1, 1 ) == '-' then selector = string.sub( selector, 2 ) blacklist = true end local ranges = mw.text.split( selector, ',' ) -- split ranges: '1,3-5' to {'1','3-5'} for _, range in pairs( ranges ) do range = mw.text.trim( range ) local min, max = mw.ustring.match( range, '^(%d+)%s*[-–—]%s*(%d+)$' ) -- '3-5' to min=3 max=5 if not max then min, max = string.match( range, '^((%d+))$' ) end -- '1' to min=1 max=1 if max then for i = min, max do map[ i ] = true end else map[ range ] = true -- if we reach this point, the string had the form 'a,b,c' rather than '1,2,3' end end -- List has the form { [1] = false, [2] = true, ['c'] = false } -- Convert it to { [1] = true, [2] = true, ['c'] = true } -- But if ANY value is set to false, treat the list as a blacklist elseif type( selector ) == 'table' then for i, v in pairs( selector ) do if v == false then blacklist = true end map[ i ] = true end end if not blacklist and ( not map or map[ value ] ) or blacklist and map and not map[ value ] then return true end end -- Get the requested tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param selector Tags to return, for example 'div' or 'div,span,gallery'. Omit to return all tags. -- @return Sequence of strings containing the wikitext of the requested tags. -- @return Original wikitext minus requested tags. function WikitextParser.getTags( wikitext, selector ) local tags = {} local tagName, tagText, tagEnd local original = wikitext local count = 0 for tagStart, tagOpen in string.gmatch( original, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" /> or <br/> or <hr> if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tagText = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len(tagOpen) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( original, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( original, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tagText = string.sub( original, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( original, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tagText = string.sub( original, tagStart, tagEnd ) end count = count + 1 if inSelector( tagName, selector ) then table.insert( tags, tagText ) else wikitext = removeString( wikitext, tagText ) end end return tags, wikitext end return WikitextParser onoc6on1ktiw3uhac0nuf2zu4b14m3v 797371 797370 2024-02-22T15:02:10Z en>Sophivorus 0 Bring and adapt getTables from [[Module:Transcluder]] 797371 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes & others -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Helper function to remove a string from a text local function removeString( text, str ) local pattern = escapeString( str ) if #pattern > 9999 then -- strings longer than 10000 bytes can't be put into regexes pattern = escapeString( mw.ustring.sub( str, 1, 999 ) ) .. '.-' .. escapeString( mw.ustring.sub( str, -999 ) ) end return string.gsub( text, pattern, '' ) end -- Helper function to determine if the given value is filtered by the given selector -- @todo Simplify and generalize even further -- @param value Value to test, for example 'div' or 1 -- @param selector Comma-separated list of values, for example 'div,span' or '1,3-5' -- @return boolean local function inSelector( value, selector ) local map = {} local blacklist = false if not selector then return true end if type( selector ) == 'number' then if selector < 0 then selector = -selector blacklist = true end map = { [ selector ] = true } elseif type( selector ) == 'string' then if string.sub( selector, 1, 1 ) == '-' then selector = string.sub( selector, 2 ) blacklist = true end local ranges = mw.text.split( selector, ',' ) -- split ranges: '1,3-5' to {'1','3-5'} for _, range in pairs( ranges ) do range = mw.text.trim( range ) local min, max = mw.ustring.match( range, '^(%d+)%s*[-–—]%s*(%d+)$' ) -- '3-5' to min=3 max=5 if not max then min, max = string.match( range, '^((%d+))$' ) end -- '1' to min=1 max=1 if max then for i = min, max do map[ i ] = true end else map[ range ] = true -- if we reach this point, the string had the form 'a,b,c' rather than '1,2,3' end end -- List has the form { [1] = false, [2] = true, ['c'] = false } -- Convert it to { [1] = true, [2] = true, ['c'] = true } -- But if ANY value is set to false, treat the list as a blacklist elseif type( selector ) == 'table' then for i, v in pairs( selector ) do if v == false then blacklist = true end map[ i ] = true end end if not blacklist and ( not map or map[ value ] ) or blacklist and map and not map[ value ] then return true end end -- Get the requested tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param selector Tags to return, for example 'div' or 'div,span,gallery'. Omit to return all tags. -- @return Sequence of strings containing the wikitext of the requested tags. -- @return Original wikitext minus requested tags. function WikitextParser.getTags( wikitext, selector ) local tags = {} local tagName, tagText, tagEnd local original = wikitext local count = 0 for tagStart, tagOpen in string.gmatch( original, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" /> or <br/> or <hr> if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tagText = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len(tagOpen) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( original, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( original, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tagText = string.sub( original, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( original, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tagText = string.sub( original, tagStart, tagEnd ) end count = count + 1 if inSelector( tagName, selector ) then table.insert( tags, tagText ) else wikitext = removeString( wikitext, tagText ) end end return tags, wikitext end -- Get the requested tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param selector Tables to return, for example 2 or '1,3-5'. Omit to return all tables. -- @return Sequence of strings containing the wikitext of the requested tables. -- @return Original wikitext minus requested tables. local function getTables( wikitext, selector ) local tables = {} local id local count = 0 for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then id = string.match( t, '\n{|[^\n]-id%s*=%s*["\']?([^"\'\n]+)["\']?[^\n]*\n' ) count = count + 1 if inSelector( id, selector ) or inSelector( count, selector ) then table.insert( tables, t ) else wikitext = removeString( wikitext, t ) end end end return tables, wikitext end return WikitextParser 8zdatyuwqfu2jfup4suc9p1apbfr6cl 797372 797371 2024-02-22T15:10:24Z en>Sophivorus 0 Expose getTables 797372 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes & others -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Helper function to remove a string from a text local function removeString( text, str ) local pattern = escapeString( str ) if #pattern > 9999 then -- strings longer than 10000 bytes can't be put into regexes pattern = escapeString( mw.ustring.sub( str, 1, 999 ) ) .. '.-' .. escapeString( mw.ustring.sub( str, -999 ) ) end return string.gsub( text, pattern, '' ) end -- Helper function to determine if the given value is filtered by the given selector -- @todo Simplify and generalize even further -- @param value Value to test, for example 'div' or 1 -- @param selector Comma-separated list of values, for example 'div,span' or '1,3-5' -- @return boolean local function inSelector( value, selector ) local map = {} local blacklist = false if not selector then return true end if type( selector ) == 'number' then if selector < 0 then selector = -selector blacklist = true end map = { [ selector ] = true } elseif type( selector ) == 'string' then if string.sub( selector, 1, 1 ) == '-' then selector = string.sub( selector, 2 ) blacklist = true end local ranges = mw.text.split( selector, ',' ) -- split ranges: '1,3-5' to {'1','3-5'} for _, range in pairs( ranges ) do range = mw.text.trim( range ) local min, max = mw.ustring.match( range, '^(%d+)%s*[-–—]%s*(%d+)$' ) -- '3-5' to min=3 max=5 if not max then min, max = string.match( range, '^((%d+))$' ) end -- '1' to min=1 max=1 if max then for i = min, max do map[ i ] = true end else map[ range ] = true -- if we reach this point, the string had the form 'a,b,c' rather than '1,2,3' end end -- List has the form { [1] = false, [2] = true, ['c'] = false } -- Convert it to { [1] = true, [2] = true, ['c'] = true } -- But if ANY value is set to false, treat the list as a blacklist elseif type( selector ) == 'table' then for i, v in pairs( selector ) do if v == false then blacklist = true end map[ i ] = true end end if not blacklist and ( not map or map[ value ] ) or blacklist and map and not map[ value ] then return true end end -- Get the requested tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param selector Tags to return, for example 'div' or 'div,span,gallery'. Omit to return all tags. -- @return Sequence of strings containing the wikitext of the requested tags. -- @return Original wikitext minus requested tags. function WikitextParser.getTags( wikitext, selector ) local tags = {} local tagName, tagText, tagEnd local original = wikitext local count = 0 for tagStart, tagOpen in string.gmatch( original, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" /> or <br/> or <hr> if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tagText = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len(tagOpen) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( original, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( original, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tagText = string.sub( original, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( original, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tagText = string.sub( original, tagStart, tagEnd ) end count = count + 1 if inSelector( tagName, selector ) then table.insert( tags, tagText ) else wikitext = removeString( wikitext, tagText ) end end return tags, wikitext end -- Get the requested tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param selector Tables to return, for example 2 or '1,3-5'. Omit to return all tables. -- @return Sequence of strings containing the wikitext of the requested tables. -- @return Original wikitext minus requested tables. function WikitextParser.getTables( wikitext, selector ) local tables = {} local id local count = 0 for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then id = string.match( t, '\n{|[^\n]-id%s*=%s*["\']?([^"\'\n]+)["\']?[^\n]*\n' ) count = count + 1 if inSelector( id, selector ) or inSelector( count, selector ) then table.insert( tables, t ) else wikitext = removeString( wikitext, t ) end end end return tables, wikitext end return WikitextParser 6u4sw52pmvjygde4npacmnr60nrn8hn 797373 797372 2024-02-27T13:27:54Z en>Sophivorus 0 Testing simpler idea 797373 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Author: User:Sophivorus -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to get the local name of a namespace and all its aliases -- @param name Canonical name of the namespace, for example 'File' -- @return Local name of the namespace and all aliases, for example {'File','Image','Archivo','Imagen'} local function getNamespaces( name ) local namespaces = mw.clone( mw.site.namespaces[ name ].aliases ) -- Clone because of https://en.wikipedia.org/w/index.php?diff=1056921358 table.insert( namespaces, mw.site.namespaces[ name ].name ) table.insert( namespaces, mw.site.namespaces[ name ].canonicalName ) return namespaces end -- Get all tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested tags. function WikitextParser.getTags( wikitext ) local tags = {} local tagName, tagText, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" /> or <br/> or <hr> if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tagText = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tagText = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tagText = string.sub( wikitext, tagStart, tagEnd ) end end return tags end -- Get all tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then table.insert( tables, t ) end end return tables end -- Get all internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested links. local function getLinks( wikitext ) local links = {} for link in string.gmatch( text, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get all category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested category links. local function getCategories( wikitext ) local categories = {} local namespace local namespaces = WikitextParser.getNamespaces( 'Category' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do namespace = string.match( link, '%[%[ ?.+ ?:.+%]%]' ) if namespaces[ namespace ] then table.insert( categories, link ) end end return categories end return WikitextParser l65x4u2ebf5190qassmev63ohntrtqd 797374 797373 2024-02-27T13:39:17Z en>Sophivorus 0 Fix getTags 797374 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Author: User:Sophivorus -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to get the local name of a namespace and all its aliases -- @param name Canonical name of the namespace, for example 'File' -- @return Local name of the namespace and all aliases, for example {'File','Image','Archivo','Imagen'} local function getNamespaces( name ) local namespaces = mw.clone( mw.site.namespaces[ name ].aliases ) -- Clone because of https://en.wikipedia.org/w/index.php?diff=1056921358 table.insert( namespaces, mw.site.namespaces[ name ].name ) table.insert( namespaces, mw.site.namespaces[ name ].canonicalName ) return namespaces end -- Get all tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" /> or <br/> or <hr> if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get all tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then table.insert( tables, t ) end end return tables end -- Get all internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested links. local function getLinks( wikitext ) local links = {} for link in string.gmatch( text, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get all category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested category links. local function getCategories( wikitext ) local categories = {} local namespace local namespaces = WikitextParser.getNamespaces( 'Category' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do namespace = string.match( link, '%[%[ ?.+ ?:.+%]%]' ) if namespaces[ namespace ] then table.insert( categories, link ) end end return categories end return WikitextParser 4n9o49wloz4o9c850r8wqsc9whgeq5d 797375 797374 2024-02-27T13:42:05Z en>Sophivorus 0 797375 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Author: User:Sophivorus -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get all tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" /> or <br/> or <hr> if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get all tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then table.insert( tables, t ) end end return tables end -- Get all internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested links. local function getLinks( wikitext ) local links = {} for link in string.gmatch( text, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get all category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested category links. local function getCategories( wikitext ) local categories = {} local namespace local namespaces = WikitextParser.getNamespaces( 'Category' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do namespace = string.match( link, '%[%[ ?.+ ?:.+%]%]' ) if namespaces[ namespace ] then table.insert( categories, link ) end end return categories end -- Helper function to get the local name of a namespace and all its aliases -- @param name Canonical name of the namespace, for example 'File' -- @return Local name of the namespace and all aliases, for example {'File','Image','Archivo','Imagen'} local function getNamespaces( name ) local namespaces = mw.clone( mw.site.namespaces[ name ].aliases ) -- Clone because of https://en.wikipedia.org/w/index.php?diff=1056921358 table.insert( namespaces, mw.site.namespaces[ name ].name ) table.insert( namespaces, mw.site.namespaces[ name ].canonicalName ) return namespaces end return WikitextParser 1v1nnmz67xxucvs6abv0sla2qm2da9q 797376 797375 2024-02-27T13:45:44Z en>Sophivorus 0 Add getFiles 797376 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Author: User:Sophivorus -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get all tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" /> or <br/> or <hr> if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get all tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then table.insert( tables, t ) end end return tables end -- Get all internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the links. local function getLinks( wikitext ) local links = {} for link in string.gmatch( text, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get all file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. local function getFiles( wikitext ) local files = {} local namespace local namespaces = WikitextParser.getNamespaces( 'File' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do namespace = string.match( link, '%[%[ ?.+ ?:.+%]%]' ) if namespaces[ namespace ] then table.insert( files, link ) end end return files end -- Get all category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested category links. local function getCategories( wikitext ) local categories = {} local namespace local namespaces = WikitextParser.getNamespaces( 'Category' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do namespace = string.match( link, '%[%[ ?.+ ?:.+%]%]' ) if namespaces[ namespace ] then table.insert( categories, link ) end end return categories end -- Helper function to get the local name of a namespace and all its aliases -- @param name Canonical name of the namespace, for example 'File' -- @return Local name of the namespace and all aliases, for example {'File','Image','Archivo','Imagen'} local function getNamespaces( name ) local namespaces = mw.clone( mw.site.namespaces[ name ].aliases ) -- Clone because of https://en.wikipedia.org/w/index.php?diff=1056921358 table.insert( namespaces, mw.site.namespaces[ name ].name ) table.insert( namespaces, mw.site.namespaces[ name ].canonicalName ) return namespaces end return WikitextParser 68traopq4p2dsinncjrxwqn6fjh9l42 797377 797376 2024-02-27T13:54:02Z en>Sophivorus 0 797377 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Author: User:Sophivorus -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get all tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" /> or <br/> or <hr> if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get all tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then table.insert( tables, t ) end end return tables end -- Get all internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( text, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get all file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local namespace local aliases = WikitextParser.getNamespaceAliases( 'File' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do namespace = string.match( link, '%[%[ ?.+ ?:.+%]%]' ) if aliases[ namespace ] then table.insert( files, link ) end end return files end -- Get all category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested category links. function WikitextParser.getCategories( wikitext ) local categories = {} local namespace local aliases = WikitextParser.getNamespaceAliases( 'Category' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do namespace = string.match( link, '%[%[ ?.+ ?:.+%]%]' ) if aliases[ namespace ] then table.insert( categories, link ) end end return categories end -- Helper function to get the local name of a namespace and all its aliases -- @param name Canonical name of the namespace, for example 'File' -- @return Local name of the namespace and all aliases, for example {'File','Image','Archivo','Imagen'} local function getNamespaceAliases( name ) local aliases = mw.site.namespaces[ name ].aliases table.insert( aliases, mw.site.namespaces[ name ].name ) table.insert( aliases, mw.site.namespaces[ name ].canonicalName ) return aliases end return WikitextParser 49r12hup81dda8mihqoaegb2soyfk21 797378 797377 2024-02-27T13:54:28Z en>Sophivorus 0 797378 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Author: User:Sophivorus -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get all tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" /> or <br/> or <hr> if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get all tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then table.insert( tables, t ) end end return tables end -- Get all internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get all file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local namespace local aliases = WikitextParser.getNamespaceAliases( 'File' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do namespace = string.match( link, '%[%[ ?.+ ?:.+%]%]' ) if aliases[ namespace ] then table.insert( files, link ) end end return files end -- Get all category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested category links. function WikitextParser.getCategories( wikitext ) local categories = {} local namespace local aliases = WikitextParser.getNamespaceAliases( 'Category' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do namespace = string.match( link, '%[%[ ?.+ ?:.+%]%]' ) if aliases[ namespace ] then table.insert( categories, link ) end end return categories end -- Helper function to get the local name of a namespace and all its aliases -- @param name Canonical name of the namespace, for example 'File' -- @return Local name of the namespace and all aliases, for example {'File','Image','Archivo','Imagen'} local function getNamespaceAliases( name ) local aliases = mw.site.namespaces[ name ].aliases table.insert( aliases, mw.site.namespaces[ name ].name ) table.insert( aliases, mw.site.namespaces[ name ].canonicalName ) return aliases end return WikitextParser rb5nyptdka34qp6bb0imv4ihxcl0ip0 797379 797378 2024-02-27T14:01:16Z en>Sophivorus 0 797379 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Author: User:Sophivorus -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get all tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" /> or <br/> or <hr> if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get all tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) table.insert( tables, t ) end end return tables end -- Get all internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get all file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local aliases = WikitextParser.getNamespaceAliases( 'File' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?.+ ?:.+%]%]' ) if aliases[ namespace ] then table.insert( files, link ) end end return files end -- Get all category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested category links. function WikitextParser.getCategories( wikitext ) local categories = {} local aliases = WikitextParser.getNamespaceAliases( 'Category' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?.+ ?:.+%]%]' ) if aliases[ namespace ] then table.insert( categories, link ) end end return categories end -- Helper function to get the local name of a namespace and all its aliases -- @param name Canonical name of the namespace, for example 'File' -- @return Local name of the namespace and all aliases, for example {'File','Image','Archivo','Imagen'} function WikitextParser.getNamespaceAliases( name ) local aliases = mw.site.namespaces[ name ].aliases table.insert( aliases, mw.site.namespaces[ name ].name ) table.insert( aliases, mw.site.namespaces[ name ].canonicalName ) return aliases end return WikitextParser rn860otdlr8bq28qwm43q8fxwdztc4q 797380 797379 2024-02-27T14:02:19Z en>Sophivorus 0 797380 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Author: User:Sophivorus -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get all tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" /> or <br/> or <hr> if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get all tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then table.insert( tables, t ) end end return tables end -- Get all internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get all file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local aliases = WikitextParser.getNamespaceAliases( 'File' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?.+ ?:.+%]%]' ) if aliases[ namespace ] then table.insert( files, link ) end end return files end -- Get all category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested category links. function WikitextParser.getCategories( wikitext ) local categories = {} local aliases = WikitextParser.getNamespaceAliases( 'Category' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?.+ ?:.+%]%]' ) if aliases[ namespace ] then table.insert( categories, link ) end end return categories end -- Helper function to get the local name of a namespace and all its aliases -- @param name Canonical name of the namespace, for example 'File' -- @return Local name of the namespace and all aliases, for example {'File','Image','Archivo','Imagen'} function WikitextParser.getNamespaceAliases( name ) local aliases = mw.site.namespaces[ name ].aliases table.insert( aliases, mw.site.namespaces[ name ].name ) table.insert( aliases, mw.site.namespaces[ name ].canonicalName ) return aliases end return WikitextParser jtf2kla3pbzuc0eaqozxougdgfvyr1t 797381 797380 2024-02-27T14:03:43Z en>Sophivorus 0 Refine getTables 797381 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Author: User:Sophivorus -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get all tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" /> or <br/> or <hr> if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get all tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( wikitext, '^%b{}' ) do if string.sub( t, 1, 2 ) == '{|' then table.insert( tables, t ) end end return tables end -- Get all internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get all file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local aliases = WikitextParser.getNamespaceAliases( 'File' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?.+ ?:.+%]%]' ) if aliases[ namespace ] then table.insert( files, link ) end end return files end -- Get all category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested category links. function WikitextParser.getCategories( wikitext ) local categories = {} local aliases = WikitextParser.getNamespaceAliases( 'Category' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?.+ ?:.+%]%]' ) if aliases[ namespace ] then table.insert( categories, link ) end end return categories end -- Helper function to get the local name of a namespace and all its aliases -- @param name Canonical name of the namespace, for example 'File' -- @return Local name of the namespace and all aliases, for example {'File','Image','Archivo','Imagen'} function WikitextParser.getNamespaceAliases( name ) local aliases = mw.site.namespaces[ name ].aliases table.insert( aliases, mw.site.namespaces[ name ].name ) table.insert( aliases, mw.site.namespaces[ name ].canonicalName ) return aliases end return WikitextParser mgg0a3bexpq0ftst6h4aofuqtoa7mkp 797382 797381 2024-02-27T14:05:50Z en>Sophivorus 0 Refine getTables 797382 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Author: User:Sophivorus -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get all tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" /> or <br/> or <hr> if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get all tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get all internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get all file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local aliases = WikitextParser.getNamespaceAliases( 'File' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?.+ ?:.+%]%]' ) if aliases[ namespace ] then table.insert( files, link ) end end return files end -- Get all category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested category links. function WikitextParser.getCategories( wikitext ) local categories = {} local aliases = WikitextParser.getNamespaceAliases( 'Category' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?.+ ?:.+%]%]' ) if aliases[ namespace ] then table.insert( categories, link ) end end return categories end -- Helper function to get the local name of a namespace and all its aliases -- @param name Canonical name of the namespace, for example 'File' -- @return Local name of the namespace and all aliases, for example {'File','Image','Archivo','Imagen'} function WikitextParser.getNamespaceAliases( name ) local aliases = mw.site.namespaces[ name ].aliases table.insert( aliases, mw.site.namespaces[ name ].name ) table.insert( aliases, mw.site.namespaces[ name ].canonicalName ) return aliases end return WikitextParser edms9qqh6dsq53ycxtz7artc7paiylc 797383 797382 2024-02-27T14:13:48Z en>Sophivorus 0 797383 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Author: User:Sophivorus -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get all tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" /> or <br/> or <hr> if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get all tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get all internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get all file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local aliases = WikitextParser.getNamespaceAliases( 'File' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?.+ ?:.+%]%]' ) if namespace and aliases[ namespace ] then table.insert( files, link ) end end return files end -- Get all category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested category links. function WikitextParser.getCategories( wikitext ) local categories = {} local aliases = WikitextParser.getNamespaceAliases( 'Category' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?.+ ?:.+%]%]' ) if namespace and aliases[ namespace ] then table.insert( categories, link ) end end return categories end -- Helper function to get the local name of a namespace and all its aliases -- @param name Canonical name of the namespace, for example 'File' -- @return Local name of the namespace and all aliases, for example {'File','Image','Archivo','Imagen'} function WikitextParser.getNamespaceAliases( name ) local aliases = mw.site.namespaces[ name ].aliases table.insert( aliases, mw.site.namespaces[ name ].name ) table.insert( aliases, mw.site.namespaces[ name ].canonicalName ) return aliases end return WikitextParser dm6t4rjxlfof7r6mk17frjn565f5ck8 797384 797383 2024-02-27T14:16:08Z en>Sophivorus 0 797384 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Author: User:Sophivorus -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get all tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" /> or <br/> or <hr> if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get all tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get all internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get all file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local aliases = WikitextParser.getNamespaceAliases( 'File' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if aliases[ namespace ] then table.insert( files, link ) end end return files end -- Get all category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested category links. function WikitextParser.getCategories( wikitext ) local categories = {} local aliases = WikitextParser.getNamespaceAliases( 'Category' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if aliases[ namespace ] then table.insert( categories, link ) end end return categories end -- Helper function to get the local name of a namespace and all its aliases -- @param name Canonical name of the namespace, for example 'File' -- @return Local name of the namespace and all aliases, for example {'File','Image','Archivo','Imagen'} function WikitextParser.getNamespaceAliases( name ) local aliases = mw.site.namespaces[ name ].aliases table.insert( aliases, mw.site.namespaces[ name ].name ) table.insert( aliases, mw.site.namespaces[ name ].canonicalName ) return aliases end return WikitextParser o2ou4n7eik2ssbmunl8uf9v8yaaumfu 797385 797384 2024-02-27T14:17:53Z en>Sophivorus 0 797385 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Author: User:Sophivorus -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get all tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" /> or <br/> or <hr> if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get all tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get all internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get all file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local aliases = WikitextParser.getNamespaceAliases( 'File' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if aliases[ namespace ] then table.insert( files, link ) end end return files end -- Get all category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested category links. function WikitextParser.getCategories( wikitext ) local categories = {} local aliases = WikitextParser.getNamespaceAliases( 'Category' ) local links = WikitextParser.getLinks( wikitext ) for link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if aliases[ namespace ] then table.insert( categories, link ) end end return categories end -- Helper function to get the local name of a namespace and all its aliases -- @param name Canonical name of the namespace, for example 'File' -- @return Local name of the namespace and all aliases, for example {'File','Image','Archivo','Imagen'} function WikitextParser.getNamespaceAliases( name ) local aliases = mw.site.namespaces[ name ].aliases table.insert( aliases, mw.site.namespaces[ name ].name ) table.insert( aliases, mw.site.namespaces[ name ].canonicalName ) return aliases end return WikitextParser 9vz59jnzlgxab86e8q75ap9br1p5zcf 797386 797385 2024-02-27T14:18:40Z en>Sophivorus 0 797386 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Author: User:Sophivorus -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get all tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" /> or <br/> or <hr> if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get all tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get all internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get all file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local aliases = WikitextParser.getNamespaceAliases( 'File' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if aliases[ namespace ] then table.insert( files, link ) end end return files end -- Get all category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested category links. function WikitextParser.getCategories( wikitext ) local categories = {} local aliases = WikitextParser.getNamespaceAliases( 'Category' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and aliases[ namespace ] then table.insert( categories, link ) end end return categories end -- Helper function to get the local name of a namespace and all its aliases -- @param name Canonical name of the namespace, for example 'File' -- @return Local name of the namespace and all aliases, for example {'File','Image','Archivo','Imagen'} function WikitextParser.getNamespaceAliases( name ) local aliases = mw.site.namespaces[ name ].aliases table.insert( aliases, mw.site.namespaces[ name ].name ) table.insert( aliases, mw.site.namespaces[ name ].canonicalName ) return aliases end return WikitextParser 8dbaop17fkobwsj94kczjuhss0j9a5n 797387 797386 2024-02-27T14:19:59Z en>Sophivorus 0 797387 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Author: User:Sophivorus -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get all tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" /> or <br/> or <hr> if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get all tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get all internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get all file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local aliases = WikitextParser.getNamespaceAliases( 'File' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if aliases[ namespace ] then table.insert( files, link ) end end return files end -- Get all category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested category links. function WikitextParser.getCategories( wikitext ) local categories = {} local aliases = WikitextParser.getNamespaceAliases( 'Category' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace then table.insert( categories, link ) end end return categories end -- Helper function to get the local name of a namespace and all its aliases -- @param name Canonical name of the namespace, for example 'File' -- @return Local name of the namespace and all aliases, for example {'File','Image','Archivo','Imagen'} function WikitextParser.getNamespaceAliases( name ) local aliases = mw.site.namespaces[ name ].aliases table.insert( aliases, mw.site.namespaces[ name ].name ) table.insert( aliases, mw.site.namespaces[ name ].canonicalName ) return aliases end return WikitextParser o5llr9bq6ymf8xevnnurdv51zruk30e 797388 797387 2024-02-27T14:34:48Z en>Sophivorus 0 797388 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Author: User:Sophivorus -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get all tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" /> or <br/> or <hr> if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get all tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get all internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get all file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local aliases = WikitextParser.getNamespaceAliases( 'File' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if aliases[ namespace ] then table.insert( files, link ) end end return files end -- Get all category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested category links. function WikitextParser.getCategories( wikitext ) local categories = {} local aliases = WikitextParser.getNamespaceAliases( 'Category' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser behf8pnneq4797xt6zfow3s4pudmj85 797389 797388 2024-02-27T14:35:09Z en>Sophivorus 0 797389 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Author: User:Sophivorus -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get all tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" /> or <br/> or <hr> if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get all tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get all internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get all file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local aliases = WikitextParser.getNamespaceAliases( 'File' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if aliases[ namespace ] then table.insert( files, link ) end end return files end -- Get all category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser qawdqgoxhlefmymxyi6570iyoxhfhd1 797390 797389 2024-02-27T14:35:29Z en>Sophivorus 0 797390 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Author: User:Sophivorus -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get all tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" /> or <br/> or <hr> if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get all tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get all internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get all file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local aliases = WikitextParser.getNamespaceAliases( 'File' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if aliases[ namespace ] then table.insert( files, link ) end end return files end -- Get all category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser 7ohuiq9v31qplekdit2nir1tu6uimej 797391 797390 2024-02-27T14:36:35Z en>Sophivorus 0 Fix canonical namespace check 797391 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Author: User:Sophivorus -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get all tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" /> or <br/> or <hr> if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get all tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get all internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get all file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local aliases = WikitextParser.getNamespaceAliases( 'File' ) local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get all category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser n5c0eisdlzkykr8v1tgbe65lkm13vwh 797392 797391 2024-02-27T14:38:50Z en>Sophivorus 0 797392 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Author: User:Sophivorus -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get all tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" /> or <br/> or <hr> if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get all tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get all internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get all file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get all category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of strings containing the wikitext of the requested category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser ksfkcjczrmn02phoxnhck024098qz2f 797393 797392 2024-02-27T14:52:52Z en>Sophivorus 0 Add getGalleries 797393 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Author: User:Sophivorus -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get all tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get all tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get all internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get all file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get all category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser 4aqdib0537i7ck7sr3zs9aappkcvd8r 797394 797393 2024-02-27T14:55:42Z en>Sophivorus 0 Add getReferences 797394 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Author: User:Sophivorus -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get all tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get all galleries from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of galleries. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get all references from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of references. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get all tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get all internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get all file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get all category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser o4lhojiuge5sesqj0aq334p2rem2bly 797395 797394 2024-02-27T15:16:28Z en>Sophivorus 0 Add getTemplates 797395 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get all templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. local function getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get all tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get all gallery tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get all ref tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get all tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get all internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get all file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get all category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser mkzjr364qezwahshae5c0mqky2rxaip 797396 797395 2024-02-27T15:24:43Z en>Sophivorus 0 Expose getTemplates 797396 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get all templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get all tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get all gallery tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get all ref tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get all tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get all internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get all file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get all category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser hd6sxboy10kimrco0t6dwlul030qtpx 797397 797396 2024-02-27T15:41:48Z en>Sophivorus 0 Add getParameters 797397 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the parameters from the given template. -- @param text Required. Template wikitext to parse. -- @return Map from parameter name to value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the gallery tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the ref tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end return WikitextParser bva2fpgodjlrr7ymhoovejzc2lq44p9 797398 797397 2024-02-27T15:48:58Z en>Sophivorus 0 Add getTemplate 797398 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the first occurrence of the requested template in the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) for _, template in templates do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if templateName == name then return template end end end -- Get the parameters from the given template. -- @param text Required. Template wikitext to parse. -- @return Map from parameter name to value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the gallery tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the ref tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end return WikitextParser twgw9jrvm3ilw3qe8vob2ikzggy6yp0 797399 797398 2024-02-27T15:50:17Z en>Sophivorus 0 797399 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the first occurrence of the requested template in the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if templateName == name then return template end end end -- Get the parameters from the given template. -- @param text Required. Template wikitext to parse. -- @return Map from parameter name to value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the gallery tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the ref tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end return WikitextParser rcbcd5vjl2lho2rvv9e3pai2ss6dlxo 797400 797399 2024-02-27T15:53:37Z en>Sophivorus 0 797400 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the first occurrence of the requested template in the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param text Required. Template wikitext to parse. -- @return Map from parameter name to value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the gallery tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the ref tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end return WikitextParser qq9v8yhtfw7wsuo3izh091qyndexyh2 797401 797400 2024-02-27T15:54:48Z en>Sophivorus 0 797401 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the first occurrence of the requested template in the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param text Required. Template wikitext to parse. -- @return Map from parameter name to value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the gallery tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the ref tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end return WikitextParser 3rz9bwono4h1sk26w9ph91b6spf1j86 797402 797401 2024-02-27T15:59:55Z en>Sophivorus 0 797402 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the first occurrence of a certain template in the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param text Required. Template wikitext to parse. -- @return Map from parameter name to value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local parts, name, value, count for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the gallery tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the ref tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end return WikitextParser bwl923hzauh3542pt61trhaoifjlnx5 797403 797402 2024-02-27T16:02:17Z en>Sophivorus 0 797403 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the first occurrence of a certain template in the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param text Required. Template wikitext to parse. -- @return Map from parameter name to value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the gallery tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the ref tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end return WikitextParser l7k0esx8xx4tsfy88dae96t7lzd4hgv 797404 797403 2024-02-29T12:01:17Z en>Sophivorus 0 Add getSection, getLead and getSectionTag 797404 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get a section from the given wikitext. -- If the given section title appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. local function getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) local level, wikitext = string.match( '\n' .. wikitext .. '\n', '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = string.gsub( wikitext, nextSection, '' ) -- remove later sections with headings at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. local function getLead( wikitext ) wikitext = string.match( '\n' .. wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the content of a <section> tag from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. local function getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) if string.find( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>' ) then -- avoid expensive search if possible wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text between section tags wikitext = wikitext:gsub( '^.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text before first section tag wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.*', '') -- remove text after last section tag wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param text Required. Template wikitext to parse. -- @return Map from parameter name to value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end return WikitextParser gzjrago5rpsjlm2ke67d8ie5b0rebkd 797405 797404 2024-02-29T12:08:50Z en>Sophivorus 0 797405 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Get a section from the given wikitext. -- If the given section title appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) local level, wikitext = string.match( '\n' .. wikitext .. '\n', '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = string.gsub( wikitext, nextSection, '' ) -- remove later sections with headings at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = string.match( '\n' .. wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the content of a <section> tag from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) if string.find( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>' ) then -- avoid expensive search if possible wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text between section tags wikitext = wikitext:gsub( '^.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text before first section tag wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.*', '') -- remove text after last section tag wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param text Required. Template wikitext to parse. -- @return Map from parameter name to value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end return WikitextParser h60k9pcxa9i6btre1gsdy0c5u58wx33 797406 797405 2024-02-29T12:10:38Z en>Sophivorus 0 797406 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get a section from the given wikitext. -- If the given section title appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) local level, wikitext = string.match( '\n' .. wikitext .. '\n', '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = string.gsub( wikitext, nextSection, '' ) -- remove later sections with headings at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = string.match( '\n' .. wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the content of a <section> tag from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) if string.find( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>' ) then -- avoid expensive search if possible wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text between section tags wikitext = wikitext:gsub( '^.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text before first section tag wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.*', '') -- remove text after last section tag wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param text Required. Template wikitext to parse. -- @return Map from parameter name to value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser gphs75cpm8tuwc878jbo9apz68lhntg 797407 797406 2024-02-29T12:14:47Z en>Sophivorus 0 797407 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get a section from the given wikitext. -- If the given section title appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) local level, wikitext = string.match( '\n' .. wikitext .. '\n', '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = string.gsub( wikitext, nextSection, '' ) -- remove later sections with headings at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = string.gsub( '\n' .. wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the content of a <section> tag from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) if string.find( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>' ) then -- avoid expensive search if possible wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text between section tags wikitext = wikitext:gsub( '^.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text before first section tag wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.*', '') -- remove text after last section tag wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param text Required. Template wikitext to parse. -- @return Map from parameter name to value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser 8fpsgqclji06iucmzbofprco27dtxht 797408 797407 2024-02-29T12:18:26Z en>Sophivorus 0 797408 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get a section from the given wikitext. -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) local level, wikitext = string.match( '\n' .. wikitext .. '\n', '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections with headings at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = string.gsub( '\n' .. wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the content of a <section> tag from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) if string.find( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>' ) then -- avoid expensive search if possible wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text between section tags wikitext = wikitext:gsub( '^.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text before first section tag wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.*', '') -- remove text after last section tag wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param text Required. Template wikitext to parse. -- @return Map from parameter name to value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser 1tuuon01nkvvgabw6kka9yn85dt2hyk 797409 797408 2024-02-29T12:25:31Z en>Sophivorus 0 797409 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = string.gsub( '\n' .. wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get a section from the given wikitext. -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) local level, wikitext = string.match( '\n' .. wikitext .. '\n', '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections with headings at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags for this one because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) if string.find( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>' ) then -- avoid expensive search if possible wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text between section tags wikitext = wikitext:gsub( '^.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text before first section tag wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.*', '') -- remove text after last section tag wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param text Required. Template wikitext to parse. -- @return Map from parameter name to value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser quhkjzuvgatwijg5y69s5y3notvgivm 797410 797409 2024-02-29T12:31:18Z en>Sophivorus 0 Add getLists 797410 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = string.gsub( '\n' .. wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get a section from the given wikitext. -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) local level, wikitext = string.match( '\n' .. wikitext .. '\n', '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections with headings at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags for this one because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) if string.find( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>' ) then -- avoid expensive search if possible wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text between section tags wikitext = wikitext:gsub( '^.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text before first section tag wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.*', '') -- remove text after last section tag wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the lists from the given wikitext. -- @param text Required. Wikitext to parse. -- @return Sequence of lists. local function getLists( wikitext ) local lists = {} for list in string.gmatch( '\n' .. wikitext .. '\n\n', '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param text Required. Template wikitext to parse. -- @return Map from parameter name to value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser r3mtnctrq1hji99rp3x7jp5i8cqpjjz 797411 797410 2024-02-29T12:35:55Z en>Sophivorus 0 797411 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = string.gsub( '\n' .. wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get a section from the given wikitext. -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) local level, wikitext = string.match( '\n' .. wikitext .. '\n', '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections with headings at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags for this one because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) if string.find( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>' ) then -- avoid expensive search if possible wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text between section tags wikitext = wikitext:gsub( '^.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text before first section tag wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.*', '') -- remove text after last section tag wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the lists from the given wikitext. -- @param text Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} for list in string.gmatch( '\n' .. wikitext .. '\n\n', '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param text Required. Template wikitext to parse. -- @return Map from parameter name to value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser 532x921zuxbdu7pu10z0xys9q48yg2x 797412 797411 2024-02-29T12:58:26Z en>Sophivorus 0 Add getParagraphs 797412 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = string.gsub( '\n' .. wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get a section from the given wikitext. -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) local level, wikitext = string.match( '\n' .. wikitext .. '\n', '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections with headings at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags for this one because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) if string.find( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>' ) then -- avoid expensive search if possible wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text between section tags wikitext = wikitext:gsub( '^.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text before first section tag wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.*', '') -- remove text after last section tag wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the lists from the given wikitext. -- @param text Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} for list in string.gmatch( '\n' .. wikitext .. '\n\n', '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @todo Reuse existing methods to make this method more robust -- @param text Required. Wikitext to parse. -- @return Sequence of paragraphs. local function getParagraphs( wikitext ) local paragraphs = {} for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do repeat paragraph = mw.text.trim( paragraph ) if paragraph == '' then break -- paragraph is empty end; if paragraph:gmatch( '^[*#]' ) then break; -- just a list end if paragraph:gmatch( '^{|.*|}$' ) then break; -- just a table end if paragraph:gmatch( '^{{.*}}$' ) then break; -- probably just a bunch of templates end if paragraph:gmatch( '^%[%b[]%]$' ) then break; -- probably just a file link end table.insert( paragraphs, paragraph ) until true end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param text Required. Template wikitext to parse. -- @return Map from parameter name to value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser 9wgcpe8ndeumpcql0xyazv517vb0fmx 797413 797412 2024-02-29T13:03:54Z en>Sophivorus 0 797413 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = string.gsub( '\n' .. wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get a section from the given wikitext. -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) local level, wikitext = string.match( '\n' .. wikitext .. '\n', '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections with headings at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags for this one because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) if string.find( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>' ) then -- avoid expensive search if possible wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text between section tags wikitext = wikitext:gsub( '^.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text before first section tag wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.*', '') -- remove text after last section tag wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the lists from the given wikitext. -- @param text Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} for list in string.gmatch( '\n' .. wikitext .. '\n\n', '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @todo Reuse existing methods to make this method more robust -- @param text Required. Wikitext to parse. -- @return Sequence of paragraphs. local function getParagraphs( wikitext ) local paragraphs = {} for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do repeat paragraph = mw.text.trim( paragraph ) if paragraph == '' then break -- paragraph is empty end; if paragraph:gmatch( '^=.*=$' ) then break; -- just a section title end if paragraph:gmatch( '^[*#]' ) then break; -- just a list end if paragraph:gmatch( '^{|.*|}$' ) then break; -- just a table end if paragraph:gmatch( '^{{.*}}$' ) then break; -- probably just a bunch of templates end if paragraph:gmatch( '^%[%b[]%]$' ) then break; -- probably just a file link end table.insert( paragraphs, paragraph ) until true end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param text Required. Template wikitext to parse. -- @return Map from parameter name to value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser a4heuurngyjlqtbr2ck7h5ywszzbvmb 797414 797413 2024-02-29T13:04:29Z en>Sophivorus 0 797414 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = string.gsub( '\n' .. wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get a section from the given wikitext. -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) local level, wikitext = string.match( '\n' .. wikitext .. '\n', '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections with headings at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags for this one because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) if string.find( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>' ) then -- avoid expensive search if possible wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text between section tags wikitext = wikitext:gsub( '^.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text before first section tag wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.*', '') -- remove text after last section tag wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the lists from the given wikitext. -- @param text Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} for list in string.gmatch( '\n' .. wikitext .. '\n\n', '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @todo Reuse existing methods to make this method more robust -- @param text Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do repeat paragraph = mw.text.trim( paragraph ) if paragraph == '' then break -- paragraph is empty end; if paragraph:gmatch( '^=.*=$' ) then break; -- just a section title end if paragraph:gmatch( '^[*#]' ) then break; -- just a list end if paragraph:gmatch( '^{|.*|}$' ) then break; -- just a table end if paragraph:gmatch( '^{{.*}}$' ) then break; -- probably just a bunch of templates end if paragraph:gmatch( '^%[%b[]%]$' ) then break; -- probably just a file link end table.insert( paragraphs, paragraph ) until true end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param text Required. Template wikitext to parse. -- @return Map from parameter name to value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser fijkcd7n5nmm4ajgdayyzhzwuqdq0mu 797415 797414 2024-02-29T13:08:06Z en>Sophivorus 0 797415 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = string.gsub( '\n' .. wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get a section from the given wikitext. -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) local level, wikitext = string.match( '\n' .. wikitext .. '\n', '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections with headings at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags for this one because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) if string.find( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>' ) then -- avoid expensive search if possible wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text between section tags wikitext = wikitext:gsub( '^.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text before first section tag wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.*', '') -- remove text after last section tag wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the lists from the given wikitext. -- @param text Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} for list in string.gmatch( '\n' .. wikitext .. '\n\n', '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @todo Reuse existing methods to make this method more robust -- @param text Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do repeat paragraph = mw.text.trim( paragraph ) if paragraph == '' then break -- paragraph is empty end; if paragraph:gmatch( '^[*#]' ) then break; -- just a list end if paragraph:gmatch( '^{|.*|}$' ) then break; -- just a table end if paragraph:gmatch( '^{{.*}}$' ) then break; -- probably just a bunch of templates end if paragraph:gmatch( '^%[%b[]%]$' ) then break; -- probably just a file link end table.insert( paragraphs, paragraph ) until true end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param text Required. Template wikitext to parse. -- @return Map from parameter name to value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser 4i33xcx72x3w5qzmc7w3ogx17jyaqbz 797416 797415 2024-02-29T13:08:19Z en>Sophivorus 0 Add getParagraphs 797416 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = string.gsub( '\n' .. wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get a section from the given wikitext. -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) local level, wikitext = string.match( '\n' .. wikitext .. '\n', '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections with headings at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags for this one because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) if string.find( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>' ) then -- avoid expensive search if possible wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text between section tags wikitext = wikitext:gsub( '^.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text before first section tag wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.*', '') -- remove text after last section tag wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the lists from the given wikitext. -- @param text Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} for list in string.gmatch( '\n' .. wikitext .. '\n\n', '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @todo Reuse existing methods to make this method more robust -- @param text Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do repeat paragraph = mw.text.trim( paragraph ) if paragraph == '' then break -- paragraph is empty end; table.insert( paragraphs, paragraph ) until true end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param text Required. Template wikitext to parse. -- @return Map from parameter name to value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser rh6c5ep665qvf3qofwc67e7k561pg3a 797417 797416 2024-02-29T13:08:43Z en>Sophivorus 0 797417 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = string.gsub( '\n' .. wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get a section from the given wikitext. -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) local level, wikitext = string.match( '\n' .. wikitext .. '\n', '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections with headings at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags for this one because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) if string.find( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>' ) then -- avoid expensive search if possible wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text between section tags wikitext = wikitext:gsub( '^.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text before first section tag wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.*', '') -- remove text after last section tag wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the lists from the given wikitext. -- @param text Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} for list in string.gmatch( '\n' .. wikitext .. '\n\n', '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @todo Reuse existing methods to make this method more robust -- @param text Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do repeat paragraph = mw.text.trim( paragraph ) if paragraph == '' then break -- paragraph is empty end; if paragraph:gmatch( '^=.*=$' ) then break; -- just a section title end if paragraph:gmatch( '^{|.*|}$' ) then break; -- just a table end if paragraph:gmatch( '^{{.*}}$' ) then break; -- probably just a bunch of templates end if paragraph:gmatch( '^%[%b[]%]$' ) then break; -- probably just a file link end table.insert( paragraphs, paragraph ) until true end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param text Required. Template wikitext to parse. -- @return Map from parameter name to value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser hluqhoyj6leamdl8h00ygtgzsx1102s 797418 797417 2024-02-29T13:08:56Z en>Sophivorus 0 Add getParagraphs 797418 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = string.gsub( '\n' .. wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get a section from the given wikitext. -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) local level, wikitext = string.match( '\n' .. wikitext .. '\n', '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections with headings at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags for this one because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) if string.find( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>' ) then -- avoid expensive search if possible wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text between section tags wikitext = wikitext:gsub( '^.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text before first section tag wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.*', '') -- remove text after last section tag wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the lists from the given wikitext. -- @param text Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} for list in string.gmatch( '\n' .. wikitext .. '\n\n', '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @todo Reuse existing methods to make this method more robust -- @param text Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do repeat paragraph = mw.text.trim( paragraph ) if paragraph == '' then break -- paragraph is empty end; if paragraph:gmatch( '^=.*=$' ) then break; -- just a section title end if paragraph:gmatch( '^[*#]' ) then break; -- just a list end if paragraph:gmatch( '^{{.*}}$' ) then break; -- probably just a bunch of templates end if paragraph:gmatch( '^%[%b[]%]$' ) then break; -- probably just a file link end table.insert( paragraphs, paragraph ) until true end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param text Required. Template wikitext to parse. -- @return Map from parameter name to value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser cq9vk771xqlnz8e5eirf9tc2hmqmdww 797419 797418 2024-02-29T13:09:16Z en>Sophivorus 0 797419 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = string.gsub( '\n' .. wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get a section from the given wikitext. -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) local level, wikitext = string.match( '\n' .. wikitext .. '\n', '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections with headings at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags for this one because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) if string.find( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>' ) then -- avoid expensive search if possible wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text between section tags wikitext = wikitext:gsub( '^.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text before first section tag wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.*', '') -- remove text after last section tag wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the lists from the given wikitext. -- @param text Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} for list in string.gmatch( '\n' .. wikitext .. '\n\n', '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @todo Reuse existing methods to make this method more robust -- @param text Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do repeat paragraph = mw.text.trim( paragraph ) if paragraph == '' then break -- paragraph is empty end; if paragraph:gmatch( '^=.*=$' ) then break; -- just a section title end if paragraph:gmatch( '^[*#]' ) then break; -- just a list end if paragraph:gmatch( '^{|.*|}$' ) then break; -- just a table end if paragraph:gmatch( '^%[%b[]%]$' ) then break; -- probably just a file link end table.insert( paragraphs, paragraph ) until true end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param text Required. Template wikitext to parse. -- @return Map from parameter name to value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser juegugpm49lnrjdwjspogd2u8cnexmk 797420 797419 2024-02-29T13:09:38Z en>Sophivorus 0 797420 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = string.gsub( '\n' .. wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get a section from the given wikitext. -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) local level, wikitext = string.match( '\n' .. wikitext .. '\n', '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections with headings at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags for this one because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) if string.find( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>' ) then -- avoid expensive search if possible wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text between section tags wikitext = wikitext:gsub( '^.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text before first section tag wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.*', '') -- remove text after last section tag wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the lists from the given wikitext. -- @param text Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} for list in string.gmatch( '\n' .. wikitext .. '\n\n', '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @todo Reuse existing methods to make this method more robust -- @param text Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do repeat paragraph = mw.text.trim( paragraph ) if paragraph == '' then break -- paragraph is empty end; if paragraph:gmatch( '^=.*=$' ) then break; -- just a section title end if paragraph:gmatch( '^[*#]' ) then break; -- just a list end if paragraph:gmatch( '^{|.*|}$' ) then break; -- just a table end if paragraph:gmatch( '^{{.*}}$' ) then break; -- probably just a bunch of templates end table.insert( paragraphs, paragraph ) until true end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param text Required. Template wikitext to parse. -- @return Map from parameter name to value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser h8zqgpqxl7junxwxvtk35lyje8yp6i6 797421 797420 2024-02-29T13:28:38Z en>Sophivorus 0 Rewrite getParagraphs 797421 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = string.gsub( '\n' .. wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get a section from the given wikitext. -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) local level, wikitext = string.match( '\n' .. wikitext .. '\n', '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections with headings at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags for this one because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) if string.find( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>' ) then -- avoid expensive search if possible wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text between section tags wikitext = wikitext:gsub( '^.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text before first section tag wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.*', '') -- remove text after last section tag wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the lists from the given wikitext. -- @param text Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} for list in string.gmatch( '\n' .. wikitext .. '\n\n', '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @todo Reuse existing methods to make this method more robust -- @param text Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param text Required. Template wikitext to parse. -- @return Map from parameter name to value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser gv1uy9q7rpft74dkk88gcjnq00a64uv 797422 797421 2024-02-29T13:52:43Z en>Sophivorus 0 Add getSections 797422 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = string.gsub( '\n' .. wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content local function getSections( wikitext ) local sections = {} for title in string.gmatch( '\n' .. wikitext .. '\n==', '\n==+ *([^=]+) *==+' ) do local section = string.match( '\n' .. wikitext .. '\n==', '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) local level, wikitext = string.match( '\n' .. wikitext .. '\n', '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags for this one because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) if string.find( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>' ) then -- avoid expensive search if possible wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text between section tags wikitext = wikitext:gsub( '^.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text before first section tag wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.*', '') -- remove text after last section tag wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} for list in string.gmatch( '\n' .. wikitext .. '\n\n', '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @todo Reuse existing methods to make this method more robust -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser pgysc6xrjohnos2dt7s4yh0vwk9tjmz 797423 797422 2024-02-29T13:54:18Z en>Sophivorus 0 797423 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = string.gsub( '\n' .. wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} for title in string.gmatch( '\n' .. wikitext .. '\n==', '\n==+ *([^=]+) *==+' ) do local section = string.match( '\n' .. wikitext .. '\n==', '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) local level, wikitext = string.match( '\n' .. wikitext .. '\n', '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags for this one because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) if string.find( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>' ) then -- avoid expensive search if possible wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text between section tags wikitext = wikitext:gsub( '^.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text before first section tag wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.*', '') -- remove text after last section tag wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} for list in string.gmatch( '\n' .. wikitext .. '\n\n', '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @todo Reuse existing methods to make this method more robust -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser mk08o0qlbjs2ycnvktvnpsy4w5gkgus 797424 797423 2024-02-29T14:02:39Z en>Sophivorus 0 797424 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) local level, wikitext = string.match( '\n' .. wikitext .. '\n', '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags for this one because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) if string.find( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>' ) then -- avoid expensive search if possible wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text between section tags wikitext = wikitext:gsub( '^.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text before first section tag wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.*', '') -- remove text after last section tag wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} for list in string.gmatch( '\n' .. wikitext .. '\n\n', '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @todo Reuse existing methods to make this method more robust -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser msgwbkh6nchcg2gpqr4zy7ye1hl5txh 797425 797424 2024-02-29T14:05:45Z en>Sophivorus 0 797425 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) local level, wikitext = string.match( '\n' .. wikitext .. '\n', '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags for this one because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) if string.find( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>' ) then -- avoid expensive search if possible wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text between section tags wikitext = wikitext:gsub( '^.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text before first section tag wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.*', '') -- remove text after last section tag wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} for list in string.gmatch( '\n' .. wikitext .. '\n\n', '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end return WikitextParser m0vmo2whujc18qi5nepwiz5e8isduvc 797426 797425 2024-02-29T14:11:25Z en>Sophivorus 0 Add getExternalLinks 797426 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) local level, wikitext = string.match( '\n' .. wikitext .. '\n', '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags for this one because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) if string.find( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>' ) then -- avoid expensive search if possible wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text between section tags wikitext = wikitext:gsub( '^.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text before first section tag wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.*', '') -- remove text after last section tag wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} for list in string.gmatch( '\n' .. wikitext .. '\n\n', '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[(https://|http://|//)' ) then table.insert( links, link ) end end return links end return WikitextParser k92l5e9nknstod60ov5komm6w5nw1cm 797427 797426 2024-02-29T14:16:30Z en>Sophivorus 0 797427 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) local level, wikitext = string.match( '\n' .. wikitext .. '\n', '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags for this one because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) if string.find( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>' ) then -- avoid expensive search if possible wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text between section tags wikitext = wikitext:gsub( '^.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text before first section tag wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.*', '') -- remove text after last section tag wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} for list in string.gmatch( '\n' .. wikitext .. '\n\n', '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser qn2k0jr9cr2cn3onlb068ucjzabqatb 797428 797427 2024-02-29T14:16:58Z en>Sophivorus 0 797428 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) local level, wikitext = string.match( '\n' .. wikitext .. '\n', '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags for this one because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) if string.find( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>' ) then -- avoid expensive search if possible wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text between section tags wikitext = wikitext:gsub( '^.-< *section +begin *= *["\']? *' .. name .. ' *["\']? */>', '') -- remove text before first section tag wikitext = wikitext:gsub( '< *section +end= *["\']? *'.. name ..' *["\']? */>.*', '') -- remove text after last section tag wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} for list in string.gmatch( '\n' .. wikitext .. '\n\n', '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser fr4y8agl65bqq32scw4sotu99d9m2ds 797429 797428 2024-02-29T14:28:37Z en>Sophivorus 0 Simplify getSectionTag 797429 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) local level, wikitext = string.match( '\n' .. wikitext .. '\n', '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags for this one because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} for list in string.gmatch( '\n' .. wikitext .. '\n\n', '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 1qalh2f046poonmmn8ufdshcy87zzgg 797430 797429 2024-02-29T14:29:22Z en>Sophivorus 0 797430 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) local level, wikitext = string.match( '\n' .. wikitext .. '\n', '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} for list in string.gmatch( '\n' .. wikitext .. '\n\n', '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser m2dibridyfku8lfzmglhjuxxnm807uz 797431 797430 2024-02-29T15:55:42Z en>Sophivorus 0 Add checks 797431 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) local level, wikitext = string.match( '\n' .. wikitext .. '\n', '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} for list in string.gmatch( '\n' .. wikitext .. '\n\n', '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = string.match( template, '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = string.match( template, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '[%b[]]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = string.gsub( string.gsub( value, '@@:@@', '|' ), '@@_@@', '=' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/].->)' ) do tagName = string.match( tagOpen, '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + string.len( tagOpen ) - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = string.sub( wikitext, tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = string.match( tag, '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} for t in string.gmatch( '\n' .. wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser gqtbr009ptt80uesk2lhcr57pzfdkbq 797432 797431 2024-03-03T13:00:37Z en>Sophivorus 0 Unify code style 797432 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 60r7tpyrvd4oczhvpstz4108p3y58ik 797433 797432 2024-04-29T11:36:10Z en>Sophivorus 0 Add methods getTableId and getTableById 797433 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( tableWikitext ) return string.match( tableWikitext, '{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, tableId ) local tables = WikitextParser.getTables( wikitext ) local id for index, tableWikitext in ipairs( tables ) do id = WikitextParser.getTableId( tableWikitext ) if id == tableId then return tableWikitext end end end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser gvg9vd3zcmmbaoh6nwaduyco2qis243 797434 797433 2024-04-29T11:54:10Z en>Sophivorus 0 Add methods getTableId and getTableById 797434 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( mw.text.trim( t ), '{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in ipairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser gbiol5om6m7doff5khojm2iqinbo0cq 797435 797434 2024-04-29T11:54:45Z en>Sophivorus 0 Add methods getTableId and getTableById 797435 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in ipairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser l0d3sdzft3vasd1fbouc3nax03yyagr 797436 797435 2024-04-29T12:10:54Z en>Sophivorus 0 Add first version of getTableData 797436 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in ipairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '^{|%+.-\n', '' ) -- remove the caption tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer local rowData for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do for cellWikitext in mw.text.gsplit( rowWikitext, '||' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser s1vuvok7su5lnwiscausp70krle7n0d 797437 797436 2024-04-29T12:11:53Z en>Sophivorus 0 Add first version of getTableData 797437 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in ipairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '^{|%+.-\n', '' ) -- remove the caption tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer local rowData = {} for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do for cellWikitext in mw.text.gsplit( rowWikitext, '||' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser rn4f84lvh6ankpxk61v4ic1kr66g128 797438 797437 2024-04-29T12:14:40Z en>Sophivorus 0 797438 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in ipairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = string.gsub( tableWikitext, '{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '{|%+.-\n', '' ) -- remove the caption tableWikitext = string.gsub( tableWikitext, '\n|}', '' ) -- remove the footer local rowData = {} for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do for cellWikitext in mw.text.gsplit( rowWikitext, '||' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 6j579gpgmf9m3tlqukilpqoxmz9h7gb 797439 797438 2024-04-29T12:15:58Z en>Sophivorus 0 797439 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in ipairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '^{|%+.-\n', '' ) -- remove the caption tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer local rowData = {} for rowWikitext in mw.text.gsplit( tableWikitext, '|%-', true ) do for cellWikitext in mw.text.gsplit( rowWikitext, '||' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser i4wxgj00nd3m5ikib8exwgx21ly3a68 797440 797439 2024-04-29T12:16:18Z en>Sophivorus 0 797440 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in ipairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '^{|%+.-\n', '' ) -- remove the caption tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer local rowData = {} for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do for cellWikitext in mw.text.gsplit( rowWikitext, '||' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser cupxls1ero3v4yrzovuj84o7mrg7p6z 797441 797440 2024-04-29T12:16:45Z en>Sophivorus 0 797441 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in ipairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove the caption tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer local rowData = {} for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do for cellWikitext in mw.text.gsplit( rowWikitext, '||' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser t55makz1jlkzvtipwqzjwnetgonp1la 797442 797441 2024-04-29T12:17:31Z en>Sophivorus 0 797442 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in ipairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove the caption tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} for cellWikitext in mw.text.gsplit( rowWikitext, '||' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser bl8hu0xyf014nb69pabaxc6gcmziwjv 797443 797442 2024-04-29T12:17:55Z en>Sophivorus 0 797443 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in ipairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove the caption tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer local rowData for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do rowData = {} for cellWikitext in mw.text.gsplit( rowWikitext, '||' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 4e3rjs95f6kiymvu5xwxyg8ssc1o8d0 797444 797443 2024-04-29T12:18:22Z en>Sophivorus 0 797444 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in ipairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove the caption tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer local rowData = {} for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do for cellWikitext in mw.text.gsplit( rowWikitext, '||' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser t55makz1jlkzvtipwqzjwnetgonp1la 797445 797444 2024-04-29T12:19:41Z en>Sophivorus 0 797445 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in ipairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove the caption tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer local rowData for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do rowData = {} for cellWikitext in mw.text.gsplit( rowWikitext, '||' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 4e3rjs95f6kiymvu5xwxyg8ssc1o8d0 797446 797445 2024-04-29T12:20:27Z en>Sophivorus 0 797446 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in ipairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove the caption tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer local rowData = {} for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do rowData = mw.clone( rowData ); for cellWikitext in mw.text.gsplit( rowWikitext, '||' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser ffv9ifkv069u1tfzluavq56uvhjb4ip 797447 797446 2024-04-29T12:20:45Z en>Sophivorus 0 797447 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in ipairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove the caption tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer local rowData = {} for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = mw.clone( rowData ); for cellWikitext in mw.text.gsplit( rowWikitext, '||' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser i4hirfc2p1b4363ledgfb805qteegdb 797448 797447 2024-04-29T12:21:07Z en>Sophivorus 0 797448 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in ipairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove the caption tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer local rowData = {} for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local data = {} for cellWikitext in mw.text.gsplit( rowWikitext, '||' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( data, cellWikitext ) end table.insert( tableData, data ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser nk87yhf5vq5wuftpwc7mx8mnaptfvt0 797449 797448 2024-04-29T12:22:26Z en>Sophivorus 0 797449 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in ipairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove the caption tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} for cellWikitext in mw.text.gsplit( rowWikitext, '||' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser bl8hu0xyf014nb69pabaxc6gcmziwjv 797450 797449 2024-04-29T12:25:04Z en>Sophivorus 0 797450 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in ipairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} for cellWikitext in mw.text.gsplit( rowWikitext, '||' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser ocl8ft0sh1nloeezfpdmei393130227 797451 797450 2024-04-29T12:25:39Z en>Sophivorus 0 797451 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in ipairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} for cellWikitext in mw.text.gsplit( rowWikitext, '||' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableWikitext end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser cqci8mv9zuyjfw0ark4rt21immvjjm0 797452 797451 2024-04-29T12:26:28Z en>Sophivorus 0 797452 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in ipairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} for cellWikitext in mw.text.gsplit( rowWikitext, '||' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableWikitext end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser i875i16tbjlc6hnyuwsk9078ngqe48y 797453 797452 2024-04-29T12:26:40Z en>Sophivorus 0 797453 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in ipairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} for cellWikitext in mw.text.gsplit( rowWikitext, '||' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 5ppgjrx5o5u0ipgykafm09fdun6fgwa 797454 797453 2024-04-29T12:27:14Z en>Sophivorus 0 797454 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in ipairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} for cellWikitext in mw.text.gsplit( rowWikitext, '[|!][|!]' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser qiw0yvtbdrvgcr1ob2zdnwgms68p332 797455 797454 2024-04-29T12:30:27Z en>Sophivorus 0 797455 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in ipairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) -- convert any || to \n| rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) -- convert any !! to \n| rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) -- convert any \n! to \n| for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser s24o3xqheqcqzzp3zpwecb76xjtzrcy 797456 797455 2024-04-29T12:30:57Z en>Sophivorus 0 797456 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in ipairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = mw.text.trim( rowWikitext ); rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) -- convert any || to \n| rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) -- convert any !! to \n| rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) -- convert any \n! to \n| for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser f9s905i1irkthqwpluk9s8k7du3cfiq 797457 797456 2024-04-29T12:32:05Z en>Sophivorus 0 797457 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in ipairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) -- convert any || to \n| rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) -- convert any !! to \n| rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) -- convert any \n! to \n| rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) -- remove the leading \n| for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 9vnr4gla8i8ck3a0syscmtiu5rcxjnw 797458 797457 2024-04-29T12:32:54Z en>Sophivorus 0 797458 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in ipairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) -- convert any || to \n| rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) -- convert any !! to \n| rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) -- convert any \n! to \n| rowWikitext = string.gsub( rowWikitext, '^|', '' ) -- remove the leading | for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 9olrndqm2yuzkn2d27al93kzi5av1yv 797459 797458 2024-04-29T12:34:00Z en>Sophivorus 0 797459 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in ipairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser nrkb32vcni659sb7acyxgdx5a55h6jn 797460 797459 2024-04-29T12:34:32Z en>Sophivorus 0 797460 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in ipairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser lblu1oygetm8v0fhzv7c82c9hfiy704 797461 797460 2025-03-18T11:31:33Z en>Sophivorus 0 Add methods to replace elements for placeholders and then restore them 797461 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for element in elements do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' + index + '@@@' ) replacedElements:insert( element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) for match in wikitext:gmatch( '@@@(%d+)@@@' ) do local placeholder = match[0]; local index = tonumber( match[1] ); local element = replacedElements[ index ]; wikitext = wikitext:gsub( placeholder, element ); replacedElements:remove( index ); end return wikitext; end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in ipairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser d74nbm6cxee41sljc0gk186fajtnjuz 797462 797461 2025-03-18T11:39:22Z en>Sophivorus 0 797462 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' + index + '@@@' ) replacedElements:insert( element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) for match in wikitext:gmatch( '@@@(%d+)@@@' ) do local placeholder = match[0]; local index = tonumber( match[1] ); local element = replacedElements[ index ]; wikitext = wikitext:gsub( placeholder, element ); replacedElements:remove( index ); end return wikitext; end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 8nz5zabm3yjx66oqmhazvselplc8850 797463 797462 2025-03-18T11:39:47Z en>Sophivorus 0 797463 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) replacedElements:insert( element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) for match in wikitext:gmatch( '@@@(%d+)@@@' ) do local placeholder = match[0]; local index = tonumber( match[1] ); local element = replacedElements[ index ]; wikitext = wikitext:gsub( placeholder, element ); replacedElements:remove( index ); end return wikitext; end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 4h4zbc4wkjjm48vsfjxk5k34hnnor8l 797464 797463 2025-03-18T11:40:54Z en>Sophivorus 0 797464 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( element, replacedElements ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) for match in wikitext:gmatch( '@@@(%d+)@@@' ) do local placeholder = match[0]; local index = tonumber( match[1] ); local element = replacedElements[ index ]; wikitext = wikitext:gsub( placeholder, element ); table.remove( index, replacedElements ); end return wikitext; end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 5ze90pq9da50s86vbe6dw70pw9hupaa 797465 797464 2025-03-18T11:41:59Z en>Sophivorus 0 797465 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) for match in wikitext:gmatch( '@@@(%d+)@@@' ) do local placeholder = match[0]; local index = tonumber( match[1] ); local element = replacedElements[ index ]; wikitext = wikitext:gsub( placeholder, element ); table.remove( replacedElements, index ); end return wikitext; end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 9sk0eu4v203xwmfvg76yd7fm0j3c6qc 797466 797465 2025-03-18T11:53:25Z en>Sophivorus 0 797466 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) for capture in wikitext:gmatch( '@@@(%d+)@@@' ) do local placeholder = '@@@' .. capture[0] .. '@@@'; local index = tonumber( capture[0] ); local element = replacedElements[ index ]; wikitext = wikitext:gsub( placeholder, element ); table.remove( replacedElements, index ); end return wikitext; end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser l69jce98qv4icfw1iqu5kdw8rk5bjbw 797467 797466 2025-03-18T11:54:54Z en>Sophivorus 0 797467 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) for index in wikitext:gmatch( '@@@(%d+)@@@' ) do index = tonumber( index ); local element = replacedElements[ index ]; wikitext = wikitext:gsub( placeholder, element ); table.remove( replacedElements, index ); end return wikitext; end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser c1rew85imj26ai5d4a0qpcsdpooonpx 797468 797467 2025-03-18T11:55:16Z en>Sophivorus 0 797468 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) for index in wikitext:gmatch( '@@@(%d+)@@@' ) do local placeholder = '@@@' .. index .. '@@@' index = tonumber( index ); local element = replacedElements[ index ]; wikitext = wikitext:gsub( placeholder, element ); table.remove( replacedElements, index ); end return wikitext; end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser isdaugj8018250vz7c9bjd47ed6tgy8 797469 797468 2025-03-18T11:57:13Z en>Sophivorus 0 797469 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) for index in wikitext:gmatch( '@@@(%d+)@@@' ) do local placeholder = '@@@' .. index .. '@@@' index = tonumber( index ); local element = replacedElements[ index ]; wikitext = wikitext:gsub( placeholder, element ); table.remove( replacedElements, index ); end return index; end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser ln0ocdh6bgrn7kdvzxz2rjh4c0i5a9f 797470 797469 2025-03-18T11:57:56Z en>Sophivorus 0 797470 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) for index in wikitext:gmatch( '@@@(%d+)@@@' ) do local placeholder = '@@@' .. index .. '@@@' index = tonumber( index ) end return index end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 8wl243tmvw4qzcsl7c5o2apyeqp017t 797471 797470 2025-03-18T11:58:17Z en>Sophivorus 0 797471 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) local index for index in wikitext:gmatch( '@@@(%d+)@@@' ) do local placeholder = '@@@' .. index .. '@@@' index = tonumber( index ) end return index end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser mhuww16pc5f2ncyrkvfb89b4azwrv4s 797472 797471 2025-03-18T11:58:31Z en>Sophivorus 0 797472 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) local index for index in wikitext:gmatch( '@@@(%d+)@@@' ) do local placeholder = '@@@' .. index .. '@@@' end return index end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser ezzojnfhnnk4fmaco8n96cs9kcqzc4j 797473 797472 2025-03-18T12:00:53Z en>Sophivorus 0 797473 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) for index in wikitext:gmatch( '@@@(%d+)@@@' ) do local placeholder = '@@@' .. index .. '@@@' index = tonumber( index ); local element = replacedElements[ index ]; wikitext = wikitext:gsub( placeholder, element ); table.remove( replacedElements, index ); end return wikitext; end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser isdaugj8018250vz7c9bjd47ed6tgy8 797474 797473 2025-03-18T12:03:19Z en>Sophivorus 0 797474 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) local index for index in wikitext:gmatch( '@@@(%d+)@@@' ) do end return index; end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 0plp15b40ehduixeykiy0xz73o7qvyp 797475 797474 2025-03-18T12:05:08Z en>Sophivorus 0 797475 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) local index for index in wikitext:gmatch( '@@@(%d-)@@@' ) do end return index; end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser bihjz9xny0329h8byjkhslehzyaoosi 797476 797475 2025-03-18T12:07:02Z en>Sophivorus 0 797476 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) local index for index in wikitext:gmatch( '@@@(%d+)@@@' ) do index = 'j' end return index; end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser mys008idlc29tprd20627r28ulw792w 797477 797476 2025-03-18T12:09:37Z en>Sophivorus 0 797477 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) local index for index in string.gmatch( wikitext, '@@@(%d+)@@@' ) do index = 'j' end return index; end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 9ojpwnqjtt157jry9cs3w08slal0auc 797478 797477 2025-03-18T12:10:57Z en>Sophivorus 0 797478 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) local placeholder for placeholder in wikitext:gmatch( '@@@%d+@@@' ) do end return placeholder; end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser rsdrf7e8bhmfste8wbp1fybewj7ltiv 797479 797478 2025-03-18T12:12:30Z en>Sophivorus 0 797479 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) local placeholder for placeholder in wikitext:gmatch( '@@@%d+@@@' ) do end return wikitext; end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser hljbclx9e8ath1htig7le25kmapefdo 797480 797479 2025-03-18T12:12:46Z en>Sophivorus 0 797480 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) local placeholder for placeholder in wikitext:gmatch( '@@@%d+@@@' ) do end return wikitext:gmatch( '@@@%d+@@@' ); end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 3gjtsyxvh70dwgyiunfj0hh4z7m99jm 797481 797480 2025-03-18T12:13:48Z en>Sophivorus 0 797481 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) local placeholder for index in wikitext:gmatch( '@@@%d+@@@' ) do placeholder = index end return placeholder; end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 3ohhuo6iq5imk6yt4bfpos4a44vbjos 797482 797481 2025-03-18T12:14:04Z en>Sophivorus 0 797482 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) local placeholder for index in wikitext:gmatch( '@@@(%d+)@@@' ) do placeholder = index end return placeholder; end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser io3mcki5wqyi2306o08hap6k0rn9k04 797483 797482 2025-03-18T12:14:40Z en>Sophivorus 0 797483 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) local placeholder for index in wikitext:gmatch( '@@@(%d+)@@@' ) do placeholder = tonumber( index ) end return placeholder; end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 0bu3obc0fbw1ljdxmnod3djjmkzykz3 797484 797483 2025-03-18T12:15:08Z en>Sophivorus 0 797484 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) local placeholder for index in wikitext:gmatch( '@@@(%d+)@@@' ) do index = tonumber( index ) placeholder = replacedElements[ index ] end return placeholder; end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 8qjr6puqkijwliaymok7c3rh4kx60fm 797485 797484 2025-03-18T12:15:32Z en>Sophivorus 0 797485 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) local placeholder for index in wikitext:gmatch( '@@@(%d+)@@@' ) do index = tonumber( index ) placeholder = #replacedElements end return placeholder; end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 3w3whd9vvl7orvx4kj4j2vtse3sxro7 797486 797485 2025-03-18T12:15:50Z en>Sophivorus 0 797486 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) local placeholder for index in wikitext:gmatch( '@@@(%d+)@@@' ) do index = tonumber( index ) + 1 placeholder = replacedElements[ index ] end return placeholder; end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser aovpw2jtomp3gbhk2zn17zdsjxerlya 797487 797486 2025-03-18T12:17:44Z en>Sophivorus 0 797487 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) local placeholder for index in wikitext:gmatch( '@@@(%d+)@@@' ) do placeholder = '@@@' .. index .. '@@@' index = tonumber( index ) + 1 end return placeholder; end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser rngzmpmwf4yyf3azd3ix7juj0f07oz1 797488 797487 2025-03-18T12:18:37Z en>Sophivorus 0 797488 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) local placeholder for index in wikitext:gmatch( '@@@(%d+)@@@' ) do placeholder = '@@@' .. index .. '@@@' index = tonumber( index ) + 1 end return mw.dumpObject( replacedElements ); end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 130xg2pkvw9ofrl6j7e8ombsefnyq0b 797489 797488 2025-03-18T12:19:25Z en>Sophivorus 0 797489 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) local placeholder for index in wikitext:gmatch( '@@@(%d+)@@@' ) do placeholder = '@@@' .. index .. '@@@' index = tonumber( index ) + 1 end return mw.dumpObject( replacedElements[0] ); end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser jb58nx4j43scj8eoqz70g6wm76hhunp 797490 797489 2025-03-18T12:23:12Z en>Sophivorus 0 797490 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements + 1 wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) for index in wikitext:gmatch( '@@@(%d+)@@@' ) do local placeholder = '@@@' .. index .. '@@@' index = tonumber( index ) element = replacedElements[ index ] wikitext = wikitext:gsub( '@@@' .. index .. '@@@', element ) end return wikitext end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 9fi30uz2nbh21emr3eo370fpy852oqk 797491 797490 2025-03-18T12:23:29Z en>Sophivorus 0 797491 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements + 1 wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) for index in wikitext:gmatch( '@@@(%d+)@@@' ) do local placeholder = '@@@' .. index .. '@@@' index = tonumber( index ) local element = replacedElements[ index ] wikitext = wikitext:gsub( '@@@' .. index .. '@@@', element ) end return wikitext end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser ph4szuvntg4lmmi714t0gyycqp2b8v1 797492 797491 2025-03-18T12:24:24Z en>Sophivorus 0 797492 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements + 1 wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) for index in wikitext:gmatch( '@@@(%d+)@@@' ) do local placeholder = '@@@' .. index .. '@@@' index = tonumber( index ) local element = replacedElements[ index ] wikitext = wikitext:gsub( placeholder, element ) end return wikitext end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser trbs4fooy9jha25pq1keiz0fblh0yn7 797493 797492 2025-03-18T12:30:16Z en>Sophivorus 0 797493 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Private variable to store the elements that were replaced by placeholders local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements + 1 replacedElements[ index ] = element wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) for index in wikitext:gmatch( '@@@(%d+)@@@' ) do local placeholder = '@@@' .. index .. '@@@' index = tonumber( index ) local element = table.remove( replacedElements, index ) wikitext = wikitext:gsub( placeholder, element ) end return wikitext end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser bkyzpxhkw6usq6j98oyriuwyzghjwx2 797494 797493 2025-03-18T12:32:50Z en>Sophivorus 0 797494 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Private variable to store the elements that were replaced by placeholders local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements + 1 wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) for index in wikitext:gmatch( '@@@(%d+)@@@' ) do local placeholder = '@@@' .. index .. '@@@' index = tonumber( index ) local element = table.remove( replacedElements, index ) wikitext = wikitext:gsub( placeholder, element ) end return wikitext end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 2qeookxt67qrqdk7zzjyf94lonkl3b4 797495 797494 2025-03-18T12:33:41Z en>Sophivorus 0 797495 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Private variable to store the elements that were replaced by placeholders local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements + 1 replacedElements[ index ] = element wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) for index in wikitext:gmatch( '@@@(%d+)@@@' ) do local placeholder = '@@@' .. index .. '@@@' index = tonumber( index ) local element = table.remove( replacedElements, index ) --wikitext = wikitext:gsub( placeholder, element ) end return mw.dumpObject( replacedElements ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser fn93kfzlscnepx5cds3h4uw27yj3n7m 797496 797495 2025-03-18T12:34:05Z en>Sophivorus 0 797496 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Private variable to store the elements that were replaced by placeholders local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements + 1 replacedElements[ index ] = element wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) for index in wikitext:gmatch( '@@@(%d+)@@@' ) do local placeholder = '@@@' .. index .. '@@@' index = tonumber( index ) local element = table.remove( replacedElements, index ) --wikitext = wikitext:gsub( placeholder, element ) end return mw.dumpObject( element ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 87hk08wlyxern1yph09pujzn3lg7cw3 797497 797496 2025-03-18T12:34:27Z en>Sophivorus 0 797497 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Private variable to store the elements that were replaced by placeholders local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements + 1 replacedElements[ index ] = element wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) local element for index in wikitext:gmatch( '@@@(%d+)@@@' ) do local placeholder = '@@@' .. index .. '@@@' index = tonumber( index ) element = table.remove( replacedElements, index ) --wikitext = wikitext:gsub( placeholder, element ) end return mw.dumpObject( element ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser ncto9jzf4bx8abvh3rjclbyhkhedf88 797498 797497 2025-03-18T12:35:52Z en>Sophivorus 0 797498 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Private variable to store the elements that were replaced by placeholders local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements + 1 replacedElements[ index ] = element wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) for index in wikitext:gmatch( '@@@(%d+)@@@' ) do local placeholder = '@@@' .. index .. '@@@' index = tonumber( index ) local element = table.remove( replacedElements, index ) wikitext = wikitext:gsub( placeholder, element ) end return wikitext end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser bkyzpxhkw6usq6j98oyriuwyzghjwx2 797499 797498 2025-03-18T12:36:47Z en>Sophivorus 0 797499 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Private variable to store the elements that were replaced by placeholders local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements + 1 replacedElements[ index ] = element wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) for index in wikitext:gmatch( '@@@(%d+)@@@' ) do local placeholder = '@@@' .. index .. '@@@' index = tonumber( index ) local element = table.remove( replacedElements, index ) wikitext = element --wikitext = wikitext:gsub( placeholder, element ) end return mw.dumpObject( wikitext ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser a31hzm1xffi3ympfs089008earvgq0g 797500 797499 2025-03-18T12:37:55Z en>Sophivorus 0 797500 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Private variable to store the elements that were replaced by placeholders local replacedElements = {} function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements + 1 wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end function WikitextParser.restoreElements( wikitext ) for index in wikitext:gmatch( '@@@(%d+)@@@' ) do local placeholder = '@@@' .. index .. '@@@' index = tonumber( index ) local element = replacedElements[ index ] wikitext = wikitext:gsub( placeholder, element ) end return wikitext end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 9l5ecsjs7jt74rotv545ny1rk6oty73 797501 797500 2025-03-18T12:45:04Z en>Sophivorus 0 797501 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Private variable to store the elements that were replaced by placeholders local replacedElements = {} -- Replace the given elements in the given wikitext for placeholders -- Placeholders have the form @@@1@@@, @@@2@@@, @@@3@@@, etc. -- @param wikitext Required. Wikitext to parse. -- @param elements Required. Elements to replace. -- @return Wikitext with elements replaced for placeholders. function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements + 1 wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) table.insert( replacedElements, element ) end end return wikitext; end -- Restore the elements in the given wikitext that were replaced by placeholders -- @param wikitext Required. Wikitext with placeholders. -- @return Wikitext with elements restored. function WikitextParser.restoreElements( wikitext ) for index in wikitext:gmatch( '@@@(%d+)@@@' ) do local placeholder = '@@@' .. index .. '@@@' index = tonumber( index ) local element = replacedElements[ index ] wikitext = wikitext:gsub( placeholder, element ) end return wikitext end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser gtjhoi4zxiny80kr3q4ipfjzmou5faz 797502 797501 2025-03-18T12:48:43Z en>Sophivorus 0 797502 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Private variable to store the elements that were replaced by placeholders local replacedElements = {} -- Replace the given elements in the given wikitext for placeholders of the form @@@1@@@, @@@2@@@, etc. -- This method is used to replace nested elements that would otherwise complicate parsing enormously -- @param wikitext Required. Wikitext to parse. -- @param elements Required. Elements to replace. -- @return Wikitext with elements replaced for placeholders. function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements replacedElements[ index ] = element wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) end end return wikitext; end -- Restore the elements in the given wikitext that were replaced by placeholders -- @param wikitext Required. Wikitext with placeholders. -- @return Wikitext with elements restored. function WikitextParser.restoreElements( wikitext ) for index in wikitext:gmatch( '@@@(%d+)@@@' ) do local placeholder = '@@@' .. index .. '@@@' index = tonumber( index ) local element = replacedElements[ index ] wikitext = wikitext:gsub( placeholder, element ) end return wikitext end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser o3y4a3hfvoxajoi51tu53pwym3htln9 797503 797502 2025-03-18T12:50:51Z en>Sophivorus 0 797503 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Private variable to store the elements that were replaced by placeholders local replacedElements = {} -- Replace the given elements in the given wikitext for placeholders of the form @@@1@@@, @@@2@@@, etc. -- This method is used to replace nested elements that would otherwise complicate parsing enormously -- @param wikitext Required. Wikitext to parse. -- @param elements Required. Elements to replace. -- @return Wikitext with elements replaced for placeholders. function WikitextParser.replaceElements( wikitext, elements ) for _, element in pairs( elements ) do local escapedElement = escapeString( element ) if wikitext:match( escapedElement ) then local index = #replacedElements + 1 replacedElements[ index ] = element wikitext = wikitext:gsub( escapedElement, '@@@' .. index .. '@@@' ) end end return wikitext; end -- Restore the elements in the given wikitext that were replaced by placeholders -- @param wikitext Required. Wikitext with placeholders. -- @return Wikitext with elements restored. function WikitextParser.restoreElements( wikitext ) for index in wikitext:gmatch( '@@@(%d+)@@@' ) do local placeholder = '@@@' .. index .. '@@@' index = tonumber( index ) local element = replacedElements[ index ] wikitext = wikitext:gsub( placeholder, element ) end return wikitext end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser sp50vy59kpdz7t7zvfzcypa9jig3txr 797504 797503 2025-03-18T13:09:45Z en>Sophivorus 0 Replacing elements not currently needed 797504 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 5pujucucldzogrna1cn8rinoq4h349p 797505 797504 2025-03-18T14:27:20Z en>Sophivorus 0 797505 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '%[%[ ?(.+) ?:.+%]%]' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser sqccnnsowst5daekyzb0dxywn9d8mlx 797506 797505 2025-03-18T14:28:39Z en>Sophivorus 0 Refine namespace matching 797506 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = template:match( '^{{ *([^}|\n]+)' ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get the parameters from the given template. -- @param wikitext Required. Template wikitext to parse. -- @return Map from parameter name to parameter value function WikitextParser.getParameters( template ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value end end return parameters end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser moh487lhunl38p3wes3oap2v80sgreu 797507 797506 2025-03-20T11:58:51Z en>Sophivorus 0 Add getTemplateName 797507 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 53p06jik0ctedsub2gjtv2uy68exlsi 797508 797507 2025-03-20T12:01:47Z en>Sophivorus 0 797508 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local params = template:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser jhmtny6xr75q3xyp0edioe8d0s75gd2 797509 797508 2025-03-20T12:08:05Z en>Sophivorus 0 797509 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser hhnxaeda8a41wysyc75xk5qg3x8exj9 797510 797509 2025-03-20T12:09:04Z en>Sophivorus 0 797510 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = mw.text.trim( table.concat( parts, '=', 2 ) ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser likkmla2zomnf0d0qc32zvunswmerns 797511 797510 2025-03-20T12:18:38Z en>Sophivorus 0 797511 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser pxz5ye9ndf2wj4x3kpzvq1wq9rh2skb 797512 797511 2025-03-20T12:24:05Z en>Sophivorus 0 797512 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return params,parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser glv1o55twir8uz34fdwz34o5pf21orv 797513 797512 2025-03-20T12:24:38Z en>Sophivorus 0 797513 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser pxz5ye9ndf2wj4x3kpzvq1wq9rh2skb 797514 797513 2025-03-20T12:33:40Z en>Sophivorus 0 797514 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '%', '%%' ):gsub( '|', '@@:@@' ):gsub( '=', '@@_@@' ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '%', '%%' ):gsub( '|', '@@:@@' ):gsub( '=', '@@_@@' ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 9cl49o0bhim8x848jg3kdyvcfdspayk 797515 797514 2025-03-20T12:34:46Z en>Sophivorus 0 797515 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '|', '@@:@@' ):gsub( '=', '@@_@@' ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '|', '@@:@@' ):gsub( '=', '@@_@@' ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 23i1yo0onvwfo3b29hncfkc96vnenb4 797516 797515 2025-03-20T12:35:44Z en>Sophivorus 0 797516 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '%%', '%%' ):gsub( '|', '@@:@@' ):gsub( '=', '@@_@@' ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '%%', '%%' ):gsub( '|', '@@:@@' ):gsub( '=', '@@_@@' ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser jyauhkwwa31oyw7mclr21hcprqq3wga 797517 797516 2025-03-20T12:36:20Z en>Sophivorus 0 797517 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser pxz5ye9ndf2wj4x3kpzvq1wq9rh2skb 797518 797517 2025-03-20T12:40:08Z en>Sophivorus 0 797518 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '%[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser t5w1zb0ehh2d16swg0ha8hi46zidbvt 797519 797518 2025-03-20T12:40:53Z en>Sophivorus 0 797519 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '%[%b[]%]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 9477yo0o97oweveuidrbq4hapacro4t 797520 797519 2025-03-20T13:00:20Z en>Sophivorus 0 797520 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '[%b[]]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser pxz5ye9ndf2wj4x3kpzvq1wq9rh2skb 797521 797520 2025-03-20T13:03:20Z en>Sophivorus 0 797521 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '%[%b[]%]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 9477yo0o97oweveuidrbq4hapacro4t 797522 797521 2025-03-21T13:07:03Z en>Sophivorus 0 Add getFileName 797522 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]+) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '%[%b[]%]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return fileWikitext:match( '%[%[[^:]-:([^]|]+)' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser oz4r8sowcg5nmbhrnkicyx3esnnd46g 797523 797522 2025-03-22T17:55:42Z en>Sophivorus 0 797523 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Helper function to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]-) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '%[%b[]%]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return fileWikitext:match( '%[%[[^:]-:([^]|]+)' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 6e24c4lxa5qnqpb8hfqe54sit9v2pr4 797524 797523 2025-03-22T19:56:55Z en>Sophivorus 0 Improve getFileName 797524 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Private helper method to get all the local names of a namespace (the canonical name and all its aliases) -- @param name Canonical name of the namespace, for example 'File' -- @return Local name of the namespace and all aliases, for example { 'File', 'Image', 'Archivo', 'Imagen' } function Excerpt.getNamespaces( name ) local namespaces = mw.clone( mw.site.namespaces[ name ].aliases ) -- clone because https://en.wikipedia.org/w/index.php?diff=1056921358 table.insert( namespaces, mw.site.namespaces[ name ].name ) table.insert( namespaces, mw.site.namespaces[ name ].canonicalName ) return namespaces end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]-) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '%[%b[]%]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return fileWikitext:match( '^%[%[ -.-:([^]|]-)' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 1chctcgfnpgyli2inoqijzy17k4vtet 797525 797524 2025-03-22T19:58:15Z en>Sophivorus 0 797525 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]-) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '%[%b[]%]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return fileWikitext:match( '^%[%[ -.-:([^]|]-)' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 5ykiweb9570xx36uhtt4hjzp3oexrks 797526 797525 2025-03-22T20:06:36Z en>Sophivorus 0 Improve getFileName 797526 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]-) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '%[%b[]%]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return fileWikitext:match( '^%[%[ *[^:]+: *([^]|]*)' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 43c5tpw8ljdref378ddvkibt4lxqxv0 797527 797526 2025-03-22T20:07:48Z en>Sophivorus 0 Improve getFileName 797527 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]-) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '%[%b[]%]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return fileWikitext:match( '^%[%[ *[^:]+: *(.*) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 8z4tt0l27fmr3wq496n97hjdv8yw17z 797528 797527 2025-03-22T20:08:42Z en>Sophivorus 0 Improve getFileName 797528 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]-) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '%[%b[]%]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return fileWikitext:match( '^%[%[ *[^:]+: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser i1ykv8ihjm94bmbuzvqxedyqrsd7id1 797529 797528 2025-03-22T20:11:41Z en>Sophivorus 0 Improve getFiles 797529 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]-) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because both opening and closing <section> tags are self-closing tags. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '%[%b[]%]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = tagOpen:match( '< ?(.-)[ >]' ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = tag:match( '< ?(.-)[ >]' ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return fileWikitext:match( '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser c24cey3edgbp8lvioniz3aiodyvkaue 797530 797529 2025-03-23T21:40:59Z en>Sophivorus 0 Add getTagName and getTagAttribute 797530 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]-) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because unlike all other tags, both opening and closing <section> tags are self-closing. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '%[%b[]%]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = WikitextParser.getTagName( tagOpen ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end function WikitextParser.getTagName( tag ) return tag:match( '^< *(.-)[ >]' ) end function WikitextParser.getTagAttribute( tag, attribute ) return tag:match( '^< *(.-) *[^>]*' .. attribute .. ' *= *["\']?([^"\'>]+)["\']?[ >]' ) end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return fileWikitext:match( '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser difqxfl7do2j4948emw3iyta6tnwlqp 797531 797530 2025-03-23T21:49:15Z en>Sophivorus 0 797531 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]-) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because unlike all other tags, both opening and closing <section> tags are self-closing. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '%[%b[]%]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = WikitextParser.getTagName( tagOpen ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end function WikitextParser.getTagName( tag ) return tag:match( '^< *(.-)[ />]' ) end function WikitextParser.getTagAttribute( tag, attribute ) return tag:match( '^< *(.-) *[^/>]*' .. attribute .. ' *= *["\']?([^"\'>]+)["\']?[ />]' ) end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return fileWikitext:match( '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser ltbjospy1zvtx9vk2fm7k2f1m8kq6yx 797532 797531 2025-03-23T21:53:06Z en>Sophivorus 0 Add docs 797532 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]-) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because unlike all other tags, both opening and closing <section> tags are self-closing. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '%[%b[]%]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = WikitextParser.getTagName( tagOpen ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the name of the given tag -- @param tag Required. Tag to parse. -- @return Name of the tag or nil if not found function WikitextParser.getTagName( tag ) return tag:match( '^< *(.-)[ />]' ) end -- Get an attribute value from the given tag -- @param tag Required. Tag to parse. -- @return Value of the attribute or nil if not found function WikitextParser.getTagAttribute( tag, attribute ) return tag:match( '^< *(.-) *[^/>]*' .. attribute .. ' *= *["\']?([^"\'>]+)["\']?[ />]' ) end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return fileWikitext:match( '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser g8muzkhkx3jtg44ofhb2z9f3laexfkt 797533 797532 2025-03-23T22:01:16Z en>Sophivorus 0 797533 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]-) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because unlike all other tags, both opening and closing <section> tags are self-closing. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '%[%b[]%]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = WikitextParser.getTagName( tagOpen ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the name of the given tag -- @param tag Required. Tag to parse. -- @return Name of the tag or nil if not found function WikitextParser.getTagName( tag ) return tag:match( '^< *(.-)[ />]' ) end -- Get an attribute value from the given tag -- @param tag Required. Tag to parse. -- @return Value of the attribute or nil if not found function WikitextParser.getTagAttribute( tag, attribute ) return tag:match( '^< *.- *[^/>]*' .. attribute .. ' *= *["\']?([^"\'>]+)["\']?[ />]' ) end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return fileWikitext:match( '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser k1n6kpd1o109s84llnt7gta2pgryf97 797534 797533 2025-03-23T22:03:39Z en>Sophivorus 0 797534 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]-) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because unlike all other tags, both opening and closing <section> tags are self-closing. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '%[%b[]%]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = WikitextParser.getTagName( tagOpen ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the name of the given tag -- @param tag Required. Tag to parse. -- @return Name of the tag or nil if not found function WikitextParser.getTagName( tag ) return tag:match( '^< *(.-)[ />]' ) end -- Get the value of an attribute in the given tag -- @param tag Required. Tag to parse. -- @return Value of the attribute or nil if not found function WikitextParser.getTagAttribute( tag, attribute ) return tag:match( '^< *.- *[^/>]*' .. attribute .. ' *= *["\']?([^"\'>]+)["\']?[ />]' ) end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( t ) return string.match( t, '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return fileWikitext:match( '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 7grrch5in637kapf1c7krzonxijaeto 797535 797534 2025-03-23T22:24:44Z en>Sophivorus 0 797535 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]-) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because unlike all other tags, both opening and closing <section> tags are self-closing. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '%[%b[]%]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = WikitextParser.getTagName( tagOpen ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the name of the tag in the given wikitext -- @param tag Required. Tag to parse. -- @return Name of the tag or nil if not found function WikitextParser.getTagName( tagWikitext ) return tagWikitext:match( '^< *(.-)[ />]' ) end -- Get the value of an attribute in the given tag. -- @param tag Required. Tag to parse. -- @return Value of the attribute or nil if not found function WikitextParser.getTagAttribute( tagWikitext, attribute ) return tagWikitext:match( '^< *.- *[^/>]*' .. attribute .. ' *= *["\']?([^"\'>]+)["\']?[ />]' ) end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( tableWikitext ) return tableWikitext:match( '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTableById( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return fileWikitext:match( '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 2t1r2scga1wzmye4u088oq66gcru681 797536 797535 2025-03-23T22:46:02Z en>Sophivorus 0 Rename getTableById to getTable 797536 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]-) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because unlike all other tags, both opening and closing <section> tags are self-closing. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '%[%b[]%]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = WikitextParser.getTagName( tagOpen ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the name of the tag in the given wikitext -- @param tag Required. Tag to parse. -- @return Name of the tag or nil if not found function WikitextParser.getTagName( tagWikitext ) return tagWikitext:match( '^< *(.-)[ />]' ) end -- Get the value of an attribute in the given tag. -- @param tag Required. Tag to parse. -- @return Value of the attribute or nil if not found function WikitextParser.getTagAttribute( tagWikitext, attribute ) return tagWikitext:match( '^< *.- *[^/>]*' .. attribute .. ' *= *["\']?([^"\'>]+)["\']?[ />]' ) end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param t Required. Wikitext of the table to parse. -- @return Id of the table or nil if not found function WikitextParser.getTableId( tableWikitext ) return tableWikitext:match( '^{|[^\n]-id *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTable( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableId( t ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return fileWikitext:match( '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser fc4bra9nnq0pyk50aqr1h3ju59x61vq 797537 797536 2025-03-23T22:55:15Z en>Sophivorus 0 Generalize getTableId to getTableAttribute 797537 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]-) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because unlike all other tags, both opening and closing <section> tags are self-closing. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '%[%b[]%]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/].->)' ) do tagName = WikitextParser.getTagName( tagOpen ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the name of the tag in the given wikitext -- @param tag Required. Tag to parse. -- @return Name of the tag or nil if not found function WikitextParser.getTagName( tagWikitext ) return tagWikitext:match( '^< *(.-)[ />]' ) end -- Get the value of an attribute in the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function WikitextParser.getTagAttribute( tagWikitext, attribute ) return tagWikitext:match( '^< *.- *[^/>]*' .. attribute .. ' *= *["\']?([^"\'>]+)["\']?[ />]' ) end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil is not found function WikitextParser.getTableAttribute( tableWikitext, attribute ) return tableWikitext:match( '^{|[^\n]*' .. attribute .. ' *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTable( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableAttribute( t, 'id' ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return fileWikitext:match( '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 5q6h7nq7jietu3m0v952ghlnqj584sq 797538 797537 2025-03-25T14:35:41Z en>Sophivorus 0 797538 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]-) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because unlike all other tags, both opening and closing <section> tags are self-closing. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '%[%b[]%]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd for tagStart, tagOpen in wikitext:gmatch( '()(<[^/!].->)' ) do tagName = WikitextParser.getTagName( tagOpen ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the name of the tag in the given wikitext -- @param tag Required. Tag to parse. -- @return Name of the tag (in lowercase) or nil if not found function WikitextParser.getTagName( tagWikitext ) local tagName = tagWikitext:match( '^< *(.-)[ />]' ) if tagName then tagName = tagName:lower() end return tagName end -- Get the value of an attribute in the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function WikitextParser.getTagAttribute( tagWikitext, attribute ) return tagWikitext:match( '^< *.- *[^/>]*' .. attribute .. ' *= *["\']?([^"\'>]+)["\']?[ />]' ) end -- Get the content of the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @return Content of the tag. May be empty if the tag is empty. Will be nil if the tag is self-closing. -- @todo May fail with nested tags function WikitextParser.getTagContent( tagWikitext, attribute ) return tagWikitext:match( '^<.->.-</.->' ) end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the reference with the given name from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param referenceName Required. Name of the reference. -- @return Wikitext of the reference function WikitextParser.getReference( wikitext, referenceName ) local references = WikitextParser.getReferences( wikitext ) for _, reference in pairs( references ) do local content = WikitextParser.getTagContent( reference ) local name = WikitextParser.getTagAttribute( reference, 'name' ) if content and name == referenceName then return reference end end end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil is not found function WikitextParser.getTableAttribute( tableWikitext, attribute ) return tableWikitext:match( '^{|[^\n]*' .. attribute .. ' *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTable( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableAttribute( t, 'id' ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return fileWikitext:match( '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser bqeno0iqaqv48muk6y95zgvl6fuw71q 797539 797538 2025-03-25T14:56:20Z en>Sophivorus 0 Refine getTags 797539 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]-) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because unlike all other tags, both opening and closing <section> tags are self-closing. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '%[%b[]%]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd -- Don't match closing tags like </div>, comments like <!--foo-->, comparisons like 1<2 or things like <3 for tagStart, tagOpen in wikitext:gmatch( '()(<[^/!%d].->)' ) do tagName = WikitextParser.getTagName( tagOpen ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) - 1 tag = wikitext:sub( tagStart, tagEnd ) end table.insert( tags, tag ) end return tags end -- Get the name of the tag in the given wikitext -- @param tag Required. Tag to parse. -- @return Name of the tag or nil if not found function WikitextParser.getTagName( tagWikitext ) local tagName = tagWikitext:match( '^< *(.-)[ />]' ) if tagName then tagName = tagName:lower() end return tagName end -- Get the value of an attribute in the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function WikitextParser.getTagAttribute( tagWikitext, attribute ) return tagWikitext:match( '^< *.- *[^/>]*' .. attribute .. ' *= *["\']?([^"\'>]+)["\']?[ />]' ) end -- Get the content of the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @return Content of the tag. May be empty if the tag is empty. Will be nil if the tag is self-closing. -- @todo May fail with nested tags function WikitextParser.getTagContent( tagWikitext, attribute ) return tagWikitext:match( '^<.->.-</.->' ) end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the reference with the given name from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param referenceName Required. Name of the reference. -- @return Wikitext of the reference function WikitextParser.getReference( wikitext, referenceName ) local references = WikitextParser.getReferences( wikitext ) for _, reference in pairs( references ) do local content = WikitextParser.getTagContent( reference ) local name = WikitextParser.getTagAttribute( reference, 'name' ) if content and name == referenceName then return reference end end end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil is not found function WikitextParser.getTableAttribute( tableWikitext, attribute ) return tableWikitext:match( '^{|[^\n]*' .. attribute .. ' *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTable( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableAttribute( t, 'id' ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return fileWikitext:match( '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser f8wbu08intplh9qyciky78ngkcgn2ww 797540 797539 2025-03-25T15:20:33Z en>Sophivorus 0 Refine getTags 797540 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]-) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because unlike all other tags, both opening and closing <section> tags are self-closing. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '%[%b[]%]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd -- Don't match closing tags like </div>, comments like <!--foo-->, comparisons like 1<2 or things like <3 for tagStart, tagOpen in wikitext:gmatch( '()(<[^/!%d].->)' ) do tagName = WikitextParser.getTagName( tagOpen ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're probably in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) if tagEnd then tag = wikitext:sub( tagStart, tagEnd - 1 ) -- If no end tag is found, assume we matched something that wasn't a tag, like <no. 1> else tag = nil end end table.insert( tags, tag ) end return tags end -- Get the name of the tag in the given wikitext -- @param tag Required. Tag to parse. -- @return Name of the tag or nil if not found function WikitextParser.getTagName( tagWikitext ) local tagName = tagWikitext:match( '^< *(.-)[ />]' ) if tagName then tagName = tagName:lower() end return tagName end -- Get the value of an attribute in the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function WikitextParser.getTagAttribute( tagWikitext, attribute ) return tagWikitext:match( '^< *.- *[^/>]*' .. attribute .. ' *= *["\']?([^"\'>]+)["\']?[ />]' ) end -- Get the content of the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @return Content of the tag. May be empty if the tag is empty. Will be nil if the tag is self-closing. -- @todo May fail with nested tags function WikitextParser.getTagContent( tagWikitext, attribute ) return tagWikitext:match( '^<.->.-</.->' ) end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the reference with the given name from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param referenceName Required. Name of the reference. -- @return Wikitext of the reference function WikitextParser.getReference( wikitext, referenceName ) local references = WikitextParser.getReferences( wikitext ) for _, reference in pairs( references ) do local content = WikitextParser.getTagContent( reference ) local name = WikitextParser.getTagAttribute( reference, 'name' ) if content and name == referenceName then return reference end end end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil is not found function WikitextParser.getTableAttribute( tableWikitext, attribute ) return tableWikitext:match( '^{|[^\n]*' .. attribute .. ' *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTable( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableAttribute( t, 'id' ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return fileWikitext:match( '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser jyd206rs73g3bsaczdxuk04t6k98msi 797541 797540 2025-03-25T15:27:22Z en>Sophivorus 0 Code style 797541 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]-) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because unlike all other tags, both opening and closing <section> tags are self-closing. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '%[%b[]%]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd -- Don't match closing tags like </div>, comments like <!--foo-->, comparisons like 1<2 or things like <3 for tagStart, tagOpen in wikitext:gmatch( '()(<[^/!%d].->)' ) do tagName = WikitextParser.getTagName( tagOpen ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're probably in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) if tagEnd then tag = wikitext:sub( tagStart, tagEnd - 1 ) -- If no end tag is found, assume we matched something that wasn't a tag, like <no. 1> else tag = nil end end table.insert( tags, tag ) end return tags end -- Get the name of the tag in the given wikitext -- @param tag Required. Tag to parse. -- @return Name of the tag or nil if not found function WikitextParser.getTagName( tagWikitext ) local tagName = tagWikitext:match( '^< *(.-)[ />]' ) if tagName then tagName = tagName:lower() end return tagName end -- Get the value of an attribute in the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function WikitextParser.getTagAttribute( tagWikitext, attribute ) return tagWikitext:match( '^< *.- *[^/>]*' .. attribute .. ' *= *["\']?([^"\'>]+)["\']?[ />]' ) end -- Get the content of the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @return Content of the tag. May be empty if the tag is empty. Will be nil if the tag is self-closing. -- @todo May fail with nested tags function WikitextParser.getTagContent( tagWikitext, attribute ) return tagWikitext:match( '^<.->.-</.->' ) end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the reference with the given name from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param referenceName Required. Name of the reference. -- @return Wikitext of the reference function WikitextParser.getReference( wikitext, referenceName ) local references = WikitextParser.getReferences( wikitext ) for _, reference in pairs( references ) do local content = WikitextParser.getTagContent( reference ) local name = WikitextParser.getTagAttribute( reference, 'name' ) if content and name == referenceName then return reference end end end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil is not found function WikitextParser.getTableAttribute( tableWikitext, attribute ) return tableWikitext:match( '^{|[^\n]*' .. attribute .. ' *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTable( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableAttribute( t, 'id' ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = tableWikitext:gsub( '^{|.-\n', '' ) -- remove the header tableWikitext = tableWikitext:gsub( '\n|}$', '' ) -- remove the footer tableWikitext = tableWikitext:gsub( '^|%+.-\n', '' ) -- remove any caption tableWikitext = tableWikitext:gsub( '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = tableWikitext:gsub( '^|%-\n', '' ) -- remove any leading empty row tableWikitext = tableWikitext:gsub( '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( '|-', true ) do local rowData = {} rowWikitext = rowWikitext:gsub( '||', '\n|' ) rowWikitext = rowWikitext:gsub( '!!', '\n|' ) rowWikitext = rowWikitext:gsub( '\n!', '\n|' ) rowWikitext = rowWikitext:gsub( '^!', '\n|' ) rowWikitext = rowWikitext:gsub( '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return fileWikitext:match( '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 5csps4zwhexih6afny9i00wdvtm1c79 797542 797541 2025-05-08T12:35:28Z en>Sophivorus 0 Fix getTagAttribute 797542 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]-) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because unlike all other tags, both opening and closing <section> tags are self-closing. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '%[%b[]%]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd -- Don't match closing tags like </div>, comments like <!--foo-->, comparisons like 1<2 or things like <3 for tagStart, tagOpen in wikitext:gmatch( '()(<[^/!%d].->)' ) do tagName = WikitextParser.getTagName( tagOpen ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're probably in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) if tagEnd then tag = wikitext:sub( tagStart, tagEnd - 1 ) -- If no end tag is found, assume we matched something that wasn't a tag, like <no. 1> else tag = nil end end table.insert( tags, tag ) end return tags end -- Get the name of the tag in the given wikitext -- @param tag Required. Tag to parse. -- @return Name of the tag or nil if not found function WikitextParser.getTagName( tagWikitext ) local tagName = tagWikitext:match( '^< *(.-)[ />]' ) if tagName then tagName = tagName:lower() end return tagName end -- Get the value of an attribute in the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function WikitextParser.getTagAttribute( tagWikitext, attribute ) return tagWikitext:match( '^< *.- *[^/>]*' .. attribute .. ' *= *["\']?([^"\'/>]+)["\']?[ />]' ) end -- Get the content of the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @return Content of the tag. May be empty if the tag is empty. Will be nil if the tag is self-closing. -- @todo May fail with nested tags function WikitextParser.getTagContent( tagWikitext, attribute ) return tagWikitext:match( '^<.->.-</.->' ) end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the reference with the given name from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param referenceName Required. Name of the reference. -- @return Wikitext of the reference function WikitextParser.getReference( wikitext, referenceName ) local references = WikitextParser.getReferences( wikitext ) for _, reference in pairs( references ) do local content = WikitextParser.getTagContent( reference ) local name = WikitextParser.getTagAttribute( reference, 'name' ) if content and name == referenceName then return reference end end end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil is not found function WikitextParser.getTableAttribute( tableWikitext, attribute ) return tableWikitext:match( '^{|[^\n]*' .. attribute .. ' *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTable( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableAttribute( t, 'id' ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = tableWikitext:gsub( '^{|.-\n', '' ) -- remove the header tableWikitext = tableWikitext:gsub( '\n|}$', '' ) -- remove the footer tableWikitext = tableWikitext:gsub( '^|%+.-\n', '' ) -- remove any caption tableWikitext = tableWikitext:gsub( '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = tableWikitext:gsub( '^|%-\n', '' ) -- remove any leading empty row tableWikitext = tableWikitext:gsub( '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( '|-', true ) do local rowData = {} rowWikitext = rowWikitext:gsub( '||', '\n|' ) rowWikitext = rowWikitext:gsub( '!!', '\n|' ) rowWikitext = rowWikitext:gsub( '\n!', '\n|' ) rowWikitext = rowWikitext:gsub( '^!', '\n|' ) rowWikitext = rowWikitext:gsub( '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return fileWikitext:match( '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser dtfl65kdyua4pvv68xnac62qj1qow3z 797543 797542 2025-05-08T13:55:56Z en>Sophivorus 0 Refine paragraph matching 797543 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return str:gsub( '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = wikitext:gsub( '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in wikitext:gmatch( '\n==+ *([^=]-) *==+' ) do local section = wikitext:match( '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = wikitext:match( '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = wikitext:gsub( nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because unlike all other tags, both opening and closing <section> tags are self-closing. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = wikitext:match( '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in wikitext:gmatch( '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = wikitext:gsub( '\n[*#][^\n]*', '\n' ) -- remove lists wikitext = wikitext:gsub( '\n%[%b[]%]\n', '\n' ) -- remove files and categories wikitext = wikitext:gsub( '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = wikitext:gsub( '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = wikitext:gsub( '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in wikitext:gmatch( '{%b{}}' ) do if wikitext:sub( 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return templateWikitext:match( '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = templateWikitext:match( '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in params:gmatch( '{%b{}}' ) do params = params:gsub( escapeString( subtemplate ), subtemplate:gsub( '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in params:gmatch( '%[%b[]%]' ) do params = params:gsub( escapeString( link ), link:gsub( '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = value:gsub( '@@_@@', '=' ) value = value:gsub( '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd -- Don't match closing tags like </div>, comments like <!--foo-->, comparisons like 1<2 or things like <3 for tagStart, tagOpen in wikitext:gmatch( '()(<[^/!%d].->)' ) do tagName = WikitextParser.getTagName( tagOpen ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if tagOpen:match( '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = wikitext:match( '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = wikitext:sub( tagStart, tagEnd ) -- Else we're probably in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = wikitext:match( '</ ?' .. tagName .. ' ?>()', tagStart ) if tagEnd then tag = wikitext:sub( tagStart, tagEnd - 1 ) -- If no end tag is found, assume we matched something that wasn't a tag, like <no. 1> else tag = nil end end table.insert( tags, tag ) end return tags end -- Get the name of the tag in the given wikitext -- @param tag Required. Tag to parse. -- @return Name of the tag or nil if not found function WikitextParser.getTagName( tagWikitext ) local tagName = tagWikitext:match( '^< *(.-)[ />]' ) if tagName then tagName = tagName:lower() end return tagName end -- Get the value of an attribute in the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function WikitextParser.getTagAttribute( tagWikitext, attribute ) return tagWikitext:match( '^< *.- *[^/>]*' .. attribute .. ' *= *["\']?([^"\'/>]+)["\']?[ />]' ) end -- Get the content of the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @return Content of the tag. May be empty if the tag is empty. Will be nil if the tag is self-closing. -- @todo May fail with nested tags function WikitextParser.getTagContent( tagWikitext, attribute ) return tagWikitext:match( '^<.->.-</.->' ) end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the reference with the given name from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param referenceName Required. Name of the reference. -- @return Wikitext of the reference function WikitextParser.getReference( wikitext, referenceName ) local references = WikitextParser.getReferences( wikitext ) for _, reference in pairs( references ) do local content = WikitextParser.getTagContent( reference ) local name = WikitextParser.getTagAttribute( reference, 'name' ) if content and name == referenceName then return reference end end end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in wikitext:gmatch( '\n%b{}' ) do if t:sub( 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil is not found function WikitextParser.getTableAttribute( tableWikitext, attribute ) return tableWikitext:match( '^{|[^\n]*' .. attribute .. ' *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTable( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableAttribute( t, 'id' ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = tableWikitext:gsub( '^{|.-\n', '' ) -- remove the header tableWikitext = tableWikitext:gsub( '\n|}$', '' ) -- remove the footer tableWikitext = tableWikitext:gsub( '^|%+.-\n', '' ) -- remove any caption tableWikitext = tableWikitext:gsub( '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = tableWikitext:gsub( '^|%-\n', '' ) -- remove any leading empty row tableWikitext = tableWikitext:gsub( '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( '|-', true ) do local rowData = {} rowWikitext = rowWikitext:gsub( '||', '\n|' ) rowWikitext = rowWikitext:gsub( '!!', '\n|' ) rowWikitext = rowWikitext:gsub( '\n!', '\n|' ) rowWikitext = rowWikitext:gsub( '^!', '\n|' ) rowWikitext = rowWikitext:gsub( '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return fileWikitext:match( '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = link:match( '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in wikitext:gmatch( '%b[]' ) do if link:match( '^%[//' ) or link:match( '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser 2spfb7r1hww3tuhaz24ue2l6ggqou6u 797544 797543 2025-05-09T14:15:50Z en>Sophivorus 0 Make string calls explicit for easier debugging 797544 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = string.gsub( wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in string.gmatch( wikitext, '\n==+ *([^=]-) *==+' ) do local section = string.match( wikitext, '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = string.match( wikitext, '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = string.gsub( wikitext, nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because unlike all other tags, both opening and closing <section> tags are self-closing. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = string.match( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in string.gmatch( wikitext, '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = string.gsub( wikitext, '\n[*#][^\n]*', '\n' ) -- remove lists wikitext = string.gsub( wikitext, '\n%[%b[]%]\n', '\n' ) -- remove files and categories wikitext = string.gsub( wikitext, '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = string.gsub( wikitext, '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = string.gsub( wikitext, '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return string.match( templateWikitext, '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = string.match( templateWikitext, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '%[%b[]%]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = string.gsub( value, '@@_@@', '=' ) value = string.gsub( value, '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd -- Don't match closing tags like </div>, comments like <!--foo-->, comparisons like 1<2 or things like <3 for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/!%d].->)' ) do tagName = WikitextParser.getTagName( tagOpen ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're probably in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) if tagEnd then tag = string.sub( wikitext, tagStart, tagEnd - 1 ) -- If no end tag is found, assume we matched something that wasn't a tag, like <no. 1> else tag = nil end end table.insert( tags, tag ) end return tags end -- Get the name of the tag in the given wikitext -- @param tag Required. Tag to parse. -- @return Name of the tag or nil if not found function WikitextParser.getTagName( tagWikitext ) local tagName = string.match( tagWikitext, '^< *(.-)[ />]' ) if tagName then tagName = string.lower( tagName ) end return tagName end -- Get the value of an attribute in the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function WikitextParser.getTagAttribute( tagWikitext, attribute ) return string.match( tagWikitext, '^< *.- *[^/>]*' .. attribute .. ' *= *["\']?([^"\'/>]+)["\']?[ />]' ) end -- Get the content of the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @return Content of the tag. May be empty if the tag is empty. Will be nil if the tag is self-closing. -- @todo May fail with nested tags function WikitextParser.getTagContent( tagWikitext, attribute ) return string.match( tagWikitext, '^<.->.-</.->' ) end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the reference with the given name from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param referenceName Required. Name of the reference. -- @return Wikitext of the reference function WikitextParser.getReference( wikitext, referenceName ) local references = WikitextParser.getReferences( wikitext ) for _, reference in pairs( references ) do local content = WikitextParser.getTagContent( reference ) local name = WikitextParser.getTagAttribute( reference, 'name' ) if content and name == referenceName then return reference end end end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in string.gmatch( gmatch, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil is not found function WikitextParser.getTableAttribute( tableWikitext, attribute ) return string.match( tableWikitext, '^{|[^\n]*' .. attribute .. ' *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTable( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableAttribute( t, 'id' ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return string.match( fileWikitext, '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%b[]' ) do if string.match( link, '^%[//' ) or string.match( link, '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser i7pjsajw9ebjrx3e2929yuww73toflg 797545 797544 2025-05-09T14:16:51Z en>Sophivorus 0 797545 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = string.gsub( wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in string.gmatch( wikitext, '\n==+ *([^=]-) *==+' ) do local section = string.match( wikitext, '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = string.match( wikitext, '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = string.gsub( wikitext, nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because unlike all other tags, both opening and closing <section> tags are self-closing. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = string.match( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in string.gmatch( wikitext, '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = string.gsub( wikitext, '\n[*#][^\n]*', '\n' ) -- remove lists wikitext = string.gsub( wikitext, '\n%[%b[]%]\n', '\n' ) -- remove files and categories wikitext = string.gsub( wikitext, '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = string.gsub( wikitext, '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = string.gsub( wikitext, '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return string.match( templateWikitext, '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = string.match( templateWikitext, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '%[%b[]%]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = string.gsub( value, '@@_@@', '=' ) value = string.gsub( value, '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd -- Don't match closing tags like </div>, comments like <!--foo-->, comparisons like 1<2 or things like <3 for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/!%d].->)' ) do tagName = WikitextParser.getTagName( tagOpen ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're probably in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) if tagEnd then tag = string.sub( wikitext, tagStart, tagEnd - 1 ) -- If no end tag is found, assume we matched something that wasn't a tag, like <no. 1> else tag = nil end end table.insert( tags, tag ) end return tags end -- Get the name of the tag in the given wikitext -- @param tag Required. Tag to parse. -- @return Name of the tag or nil if not found function WikitextParser.getTagName( tagWikitext ) local tagName = string.match( tagWikitext, '^< *(.-)[ />]' ) if tagName then tagName = string.lower( tagName ) end return tagName end -- Get the value of an attribute in the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function WikitextParser.getTagAttribute( tagWikitext, attribute ) return string.match( tagWikitext, '^< *.- *[^/>]*' .. attribute .. ' *= *["\']?([^"\'/>]+)["\']?[ />]' ) end -- Get the content of the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @return Content of the tag. May be empty if the tag is empty. Will be nil if the tag is self-closing. -- @todo May fail with nested tags function WikitextParser.getTagContent( tagWikitext, attribute ) return string.match( tagWikitext, '^<.->.-</.->' ) end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the reference with the given name from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param referenceName Required. Name of the reference. -- @return Wikitext of the reference function WikitextParser.getReference( wikitext, referenceName ) local references = WikitextParser.getReferences( wikitext ) for _, reference in pairs( references ) do local content = WikitextParser.getTagContent( reference ) local name = WikitextParser.getTagAttribute( reference, 'name' ) if content and name == referenceName then return reference end end end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in string.gmatch( wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil is not found function WikitextParser.getTableAttribute( tableWikitext, attribute ) return string.match( tableWikitext, '^{|[^\n]*' .. attribute .. ' *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTable( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableAttribute( t, 'id' ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return string.match( fileWikitext, '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%b[]' ) do if string.match( link, '^%[//' ) or string.match( link, '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser oolrhgdob9s82agoctij7s546klops6 797546 797545 2025-05-09T18:18:14Z en>Aidan9382 0 singificantly improve the performance of getTagAttribute (prevent the .- from trying to scan everything after the opening tag when the attribute isn't present) (also just simplified it in general to avoid repeated ambiguous captures) 797546 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = string.gsub( wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in string.gmatch( wikitext, '\n==+ *([^=]-) *==+' ) do local section = string.match( wikitext, '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = string.match( wikitext, '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = string.gsub( wikitext, nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because unlike all other tags, both opening and closing <section> tags are self-closing. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = string.match( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in string.gmatch( wikitext, '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = string.gsub( wikitext, '\n[*#][^\n]*', '\n' ) -- remove lists wikitext = string.gsub( wikitext, '\n%[%b[]%]\n', '\n' ) -- remove files and categories wikitext = string.gsub( wikitext, '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = string.gsub( wikitext, '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = string.gsub( wikitext, '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return string.match( templateWikitext, '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = string.match( templateWikitext, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '%[%b[]%]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = string.gsub( value, '@@_@@', '=' ) value = string.gsub( value, '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd -- Don't match closing tags like </div>, comments like <!--foo-->, comparisons like 1<2 or things like <3 for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/!%d].->)' ) do tagName = WikitextParser.getTagName( tagOpen ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're probably in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) if tagEnd then tag = string.sub( wikitext, tagStart, tagEnd - 1 ) -- If no end tag is found, assume we matched something that wasn't a tag, like <no. 1> else tag = nil end end table.insert( tags, tag ) end return tags end -- Get the name of the tag in the given wikitext -- @param tag Required. Tag to parse. -- @return Name of the tag or nil if not found function WikitextParser.getTagName( tagWikitext ) local tagName = string.match( tagWikitext, '^< *(.-)[ />]' ) if tagName then tagName = string.lower( tagName ) end return tagName end -- Get the value of an attribute in the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function WikitextParser.getTagAttribute( tagWikitext, attribute ) return string.match( tagWikitext, '^<[^/>]*' .. attribute .. ' *= *["\']?([^"\'/>]+)["\']?[ />]' ) end -- Get the content of the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @return Content of the tag. May be empty if the tag is empty. Will be nil if the tag is self-closing. -- @todo May fail with nested tags function WikitextParser.getTagContent( tagWikitext, attribute ) return string.match( tagWikitext, '^<.->.-</.->' ) end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the reference with the given name from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param referenceName Required. Name of the reference. -- @return Wikitext of the reference function WikitextParser.getReference( wikitext, referenceName ) local references = WikitextParser.getReferences( wikitext ) for _, reference in pairs( references ) do local content = WikitextParser.getTagContent( reference ) local name = WikitextParser.getTagAttribute( reference, 'name' ) if content and name == referenceName then return reference end end end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in string.gmatch( wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil is not found function WikitextParser.getTableAttribute( tableWikitext, attribute ) return string.match( tableWikitext, '^{|[^\n]*' .. attribute .. ' *= *["\']?([^"\'\n]+)["\']?[^\n]*\n' ) end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTable( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableAttribute( t, 'id' ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return string.match( fileWikitext, '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%b[]' ) do if string.match( link, '^%[//' ) or string.match( link, '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser m2pw760dpfxn7l36pi0649bw0m09x6l 797547 797546 2025-05-09T18:28:23Z en>Aidan9382 0 Slightly improve getXYZAttribute when getting string-wrapped content (this should allow cases like <ref name="xyz's abc"> to capture name, though other cases like \-escaped quotes will still break) 797547 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = string.gsub( wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in string.gmatch( wikitext, '\n==+ *([^=]-) *==+' ) do local section = string.match( wikitext, '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = string.match( wikitext, '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = string.gsub( wikitext, nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because unlike all other tags, both opening and closing <section> tags are self-closing. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = string.match( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in string.gmatch( wikitext, '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = string.gsub( wikitext, '\n[*#][^\n]*', '\n' ) -- remove lists wikitext = string.gsub( wikitext, '\n%[%b[]%]\n', '\n' ) -- remove files and categories wikitext = string.gsub( wikitext, '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = string.gsub( wikitext, '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = string.gsub( wikitext, '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return string.match( templateWikitext, '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = string.match( templateWikitext, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '%[%b[]%]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = string.gsub( value, '@@_@@', '=' ) value = string.gsub( value, '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd -- Don't match closing tags like </div>, comments like <!--foo-->, comparisons like 1<2 or things like <3 for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/!%d].->)' ) do tagName = WikitextParser.getTagName( tagOpen ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're probably in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) if tagEnd then tag = string.sub( wikitext, tagStart, tagEnd - 1 ) -- If no end tag is found, assume we matched something that wasn't a tag, like <no. 1> else tag = nil end end table.insert( tags, tag ) end return tags end -- Get the name of the tag in the given wikitext -- @param tag Required. Tag to parse. -- @return Name of the tag or nil if not found function WikitextParser.getTagName( tagWikitext ) local tagName = string.match( tagWikitext, '^< *(.-)[ />]' ) if tagName then tagName = string.lower( tagName ) end return tagName end -- Get the value of an attribute in the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function WikitextParser.getTagAttribute( tagWikitext, attribute ) local _quote, value = string.match( tagWikitext, '^<[^/>]*' .. attribute .. ' *= *(["\']?)([^/>]-)%1[ />]' ) return value end -- Get the content of the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @return Content of the tag. May be empty if the tag is empty. Will be nil if the tag is self-closing. -- @todo May fail with nested tags function WikitextParser.getTagContent( tagWikitext, attribute ) return string.match( tagWikitext, '^<.->.-</.->' ) end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the reference with the given name from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param referenceName Required. Name of the reference. -- @return Wikitext of the reference function WikitextParser.getReference( wikitext, referenceName ) local references = WikitextParser.getReferences( wikitext ) for _, reference in pairs( references ) do local content = WikitextParser.getTagContent( reference ) local name = WikitextParser.getTagAttribute( reference, 'name' ) if content and name == referenceName then return reference end end end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in string.gmatch( wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function WikitextParser.getTableAttribute( tableWikitext, attribute ) local _quote, value = string.match( tableWikitext, '^{|[^\n]*' .. attribute .. ' *= *(["\']?)([^\n]-)%1[^\n]*\n' ) return value end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTable( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableAttribute( t, 'id' ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return string.match( fileWikitext, '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%b[]' ) do if string.match( link, '^%[//' ) or string.match( link, '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser c21w4rau9v8rya4xm6lvmpj93yrm8pl 797548 797547 2025-06-20T13:57:00Z en>Sophivorus 0 Add missing param 797548 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = string.gsub( wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in string.gmatch( wikitext, '\n==+ *([^=]-) *==+' ) do local section = string.match( wikitext, '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = string.match( wikitext, '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = string.gsub( wikitext, nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because unlike all other tags, both opening and closing <section> tags are self-closing. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = string.match( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in string.gmatch( wikitext, '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = string.gsub( wikitext, '\n[*#][^\n]*', '\n' ) -- remove lists wikitext = string.gsub( wikitext, '\n%[%b[]%]\n', '\n' ) -- remove files and categories wikitext = string.gsub( wikitext, '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = string.gsub( wikitext, '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = string.gsub( wikitext, '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return string.match( templateWikitext, '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = string.match( templateWikitext, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '%[%b[]%]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = string.gsub( value, '@@_@@', '=' ) value = string.gsub( value, '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd -- Don't match closing tags like </div>, comments like <!--foo-->, comparisons like 1<2 or things like <3 for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/!%d].->)' ) do tagName = WikitextParser.getTagName( tagOpen ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're probably in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) if tagEnd then tag = string.sub( wikitext, tagStart, tagEnd - 1 ) -- If no end tag is found, assume we matched something that wasn't a tag, like <no. 1> else tag = nil end end table.insert( tags, tag ) end return tags end -- Get the name of the tag in the given wikitext -- @param tag Required. Tag to parse. -- @return Name of the tag or nil if not found function WikitextParser.getTagName( tagWikitext ) local tagName = string.match( tagWikitext, '^< *(.-)[ />]' ) if tagName then tagName = string.lower( tagName ) end return tagName end -- Get the value of an attribute in the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function WikitextParser.getTagAttribute( tagWikitext, attribute ) local _quote, value = string.match( tagWikitext, '^<[^/>]*' .. attribute .. ' *= *(["\']?)([^/>]-)%1[ />]' ) return value end -- Get the content of the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @return Content of the tag. May be empty if the tag is empty. Will be nil if the tag is self-closing. -- @todo May fail with nested tags function WikitextParser.getTagContent( tagWikitext, attribute ) return string.match( tagWikitext, '^<.->.-</.->' ) end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the reference with the given name from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param referenceName Required. Name of the reference. -- @return Wikitext of the reference function WikitextParser.getReference( wikitext, referenceName ) local references = WikitextParser.getReferences( wikitext ) for _, reference in pairs( references ) do local content = WikitextParser.getTagContent( reference ) local name = WikitextParser.getTagAttribute( reference, 'name' ) if content and name == referenceName then return reference end end end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in string.gmatch( wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function WikitextParser.getTableAttribute( tableWikitext, attribute ) local _quote, value = string.match( tableWikitext, '^{|[^\n]*' .. attribute .. ' *= *(["\']?)([^\n]-)%1[^\n]*\n' ) return value end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTable( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableAttribute( t, 'id' ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return string.match( fileWikitext, '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%b[]' ) do if string.match( link, '^%[//' ) or string.match( link, '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser tmqgmfv09xj7f50m3k9l4o4b8efibdg 797549 797548 2025-10-02T18:00:44Z en>MusikBot II 0 Protected "[[Module:WikitextParser]]": [[Wikipedia:High-risk templates|High-risk template or module]]: 11524 transclusions ([[User:MusikBot II/TemplateProtector|more info]]) ([Edit=Require template editor access] (indefinite) [Move=Require template editor access] (indefinite)) 797548 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = string.gsub( wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in string.gmatch( wikitext, '\n==+ *([^=]-) *==+' ) do local section = string.match( wikitext, '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = string.match( wikitext, '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = string.gsub( wikitext, nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because unlike all other tags, both opening and closing <section> tags are self-closing. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = string.match( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in string.gmatch( wikitext, '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = string.gsub( wikitext, '\n[*#][^\n]*', '\n' ) -- remove lists wikitext = string.gsub( wikitext, '\n%[%b[]%]\n', '\n' ) -- remove files and categories wikitext = string.gsub( wikitext, '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = string.gsub( wikitext, '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = string.gsub( wikitext, '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( wikitext, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return string.match( templateWikitext, '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = string.match( templateWikitext, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '%[%b[]%]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = string.gsub( value, '@@_@@', '=' ) value = string.gsub( value, '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd -- Don't match closing tags like </div>, comments like <!--foo-->, comparisons like 1<2 or things like <3 for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/!%d].->)' ) do tagName = WikitextParser.getTagName( tagOpen ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're probably in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) if tagEnd then tag = string.sub( wikitext, tagStart, tagEnd - 1 ) -- If no end tag is found, assume we matched something that wasn't a tag, like <no. 1> else tag = nil end end table.insert( tags, tag ) end return tags end -- Get the name of the tag in the given wikitext -- @param tag Required. Tag to parse. -- @return Name of the tag or nil if not found function WikitextParser.getTagName( tagWikitext ) local tagName = string.match( tagWikitext, '^< *(.-)[ />]' ) if tagName then tagName = string.lower( tagName ) end return tagName end -- Get the value of an attribute in the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function WikitextParser.getTagAttribute( tagWikitext, attribute ) local _quote, value = string.match( tagWikitext, '^<[^/>]*' .. attribute .. ' *= *(["\']?)([^/>]-)%1[ />]' ) return value end -- Get the content of the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @return Content of the tag. May be empty if the tag is empty. Will be nil if the tag is self-closing. -- @todo May fail with nested tags function WikitextParser.getTagContent( tagWikitext, attribute ) return string.match( tagWikitext, '^<.->.-</.->' ) end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the reference with the given name from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param referenceName Required. Name of the reference. -- @return Wikitext of the reference function WikitextParser.getReference( wikitext, referenceName ) local references = WikitextParser.getReferences( wikitext ) for _, reference in pairs( references ) do local content = WikitextParser.getTagContent( reference ) local name = WikitextParser.getTagAttribute( reference, 'name' ) if content and name == referenceName then return reference end end end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in string.gmatch( wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function WikitextParser.getTableAttribute( tableWikitext, attribute ) local _quote, value = string.match( tableWikitext, '^{|[^\n]*' .. attribute .. ' *= *(["\']?)([^\n]-)%1[^\n]*\n' ) return value end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTable( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableAttribute( t, 'id' ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return string.match( fileWikitext, '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%b[]' ) do if string.match( link, '^%[//' ) or string.match( link, '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser tmqgmfv09xj7f50m3k9l4o4b8efibdg 797550 797549 2025-10-02T18:32:43Z en>Aidan9382 0 wrong variable used in getTemplates check 797550 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = string.gsub( wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in string.gmatch( wikitext, '\n==+ *([^=]-) *==+' ) do local section = string.match( wikitext, '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = string.match( wikitext, '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = string.gsub( wikitext, nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because unlike all other tags, both opening and closing <section> tags are self-closing. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = string.match( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in string.gmatch( wikitext, '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = string.gsub( wikitext, '\n[*#][^\n]*', '\n' ) -- remove lists wikitext = string.gsub( wikitext, '\n%[%b[]%]\n', '\n' ) -- remove files and categories wikitext = string.gsub( wikitext, '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = string.gsub( wikitext, '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = string.gsub( wikitext, '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( template, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return string.match( templateWikitext, '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = string.match( templateWikitext, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '%[%b[]%]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = string.gsub( value, '@@_@@', '=' ) value = string.gsub( value, '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd -- Don't match closing tags like </div>, comments like <!--foo-->, comparisons like 1<2 or things like <3 for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/!%d].->)' ) do tagName = WikitextParser.getTagName( tagOpen ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're probably in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) if tagEnd then tag = string.sub( wikitext, tagStart, tagEnd - 1 ) -- If no end tag is found, assume we matched something that wasn't a tag, like <no. 1> else tag = nil end end table.insert( tags, tag ) end return tags end -- Get the name of the tag in the given wikitext -- @param tag Required. Tag to parse. -- @return Name of the tag or nil if not found function WikitextParser.getTagName( tagWikitext ) local tagName = string.match( tagWikitext, '^< *(.-)[ />]' ) if tagName then tagName = string.lower( tagName ) end return tagName end -- Get the value of an attribute in the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function WikitextParser.getTagAttribute( tagWikitext, attribute ) local _quote, value = string.match( tagWikitext, '^<[^/>]*' .. attribute .. ' *= *(["\']?)([^/>]-)%1[ />]' ) return value end -- Get the content of the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @return Content of the tag. May be empty if the tag is empty. Will be nil if the tag is self-closing. -- @todo May fail with nested tags function WikitextParser.getTagContent( tagWikitext, attribute ) return string.match( tagWikitext, '^<.->.-</.->' ) end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the reference with the given name from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param referenceName Required. Name of the reference. -- @return Wikitext of the reference function WikitextParser.getReference( wikitext, referenceName ) local references = WikitextParser.getReferences( wikitext ) for _, reference in pairs( references ) do local content = WikitextParser.getTagContent( reference ) local name = WikitextParser.getTagAttribute( reference, 'name' ) if content and name == referenceName then return reference end end end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in string.gmatch( wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function WikitextParser.getTableAttribute( tableWikitext, attribute ) local _quote, value = string.match( tableWikitext, '^{|[^\n]*' .. attribute .. ' *= *(["\']?)([^\n]-)%1[^\n]*\n' ) return value end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTable( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableAttribute( t, 'id' ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return string.match( fileWikitext, '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%b[]' ) do if string.match( link, '^%[//' ) or string.match( link, '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser f88uhct4rmxdk9ohkcqh8ma54hr84u6 797551 797550 2025-10-03T12:27:15Z en>Sophivorus 0 Move list removal below to fix the bug reported at [[Module talk:Excerpt#Bug report: poss interference from escaped pipe earlier on page]] 797551 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = string.gsub( wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in string.gmatch( wikitext, '\n==+ *([^=]-) *==+' ) do local section = string.match( wikitext, '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = string.match( wikitext, '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = string.gsub( wikitext, nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because unlike all other tags, both opening and closing <section> tags are self-closing. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = string.match( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in string.gmatch( wikitext, '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = string.gsub( wikitext, '\n%[%b[]%]\n', '\n' ) -- remove files and categories wikitext = string.gsub( wikitext, '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = string.gsub( wikitext, '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = string.gsub( wikitext, '\n[*#][^\n]*', '\n' ) -- remove lists wikitext = string.gsub( wikitext, '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( template, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return string.match( templateWikitext, '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = string.match( templateWikitext, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '%[%b[]%]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = string.gsub( value, '@@_@@', '=' ) value = string.gsub( value, '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd -- Don't match closing tags like </div>, comments like <!--foo-->, comparisons like 1<2 or things like <3 for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/!%d].->)' ) do tagName = WikitextParser.getTagName( tagOpen ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're probably in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) if tagEnd then tag = string.sub( wikitext, tagStart, tagEnd - 1 ) -- If no end tag is found, assume we matched something that wasn't a tag, like <no. 1> else tag = nil end end table.insert( tags, tag ) end return tags end -- Get the name of the tag in the given wikitext -- @param tag Required. Tag to parse. -- @return Name of the tag or nil if not found function WikitextParser.getTagName( tagWikitext ) local tagName = string.match( tagWikitext, '^< *(.-)[ />]' ) if tagName then tagName = string.lower( tagName ) end return tagName end -- Get the value of an attribute in the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function WikitextParser.getTagAttribute( tagWikitext, attribute ) local _quote, value = string.match( tagWikitext, '^<[^/>]*' .. attribute .. ' *= *(["\']?)([^/>]-)%1[ />]' ) return value end -- Get the content of the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @return Content of the tag. May be empty if the tag is empty. Will be nil if the tag is self-closing. -- @todo May fail with nested tags function WikitextParser.getTagContent( tagWikitext, attribute ) return string.match( tagWikitext, '^<.->.-</.->' ) end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the reference with the given name from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param referenceName Required. Name of the reference. -- @return Wikitext of the reference function WikitextParser.getReference( wikitext, referenceName ) local references = WikitextParser.getReferences( wikitext ) for _, reference in pairs( references ) do local content = WikitextParser.getTagContent( reference ) local name = WikitextParser.getTagAttribute( reference, 'name' ) if content and name == referenceName then return reference end end end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in string.gmatch( wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function WikitextParser.getTableAttribute( tableWikitext, attribute ) local _quote, value = string.match( tableWikitext, '^{|[^\n]*' .. attribute .. ' *= *(["\']?)([^\n]-)%1[^\n]*\n' ) return value end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTable( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableAttribute( t, 'id' ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return string.match( fileWikitext, '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%b[]' ) do if string.match( link, '^%[//' ) or string.match( link, '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser qf73q03ka5bli5zsckgxc9imrhotias 797552 797551 2025-10-06T12:29:27Z en>Sophivorus 0 Refine getParagraphs 797552 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = string.gsub( wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in string.gmatch( wikitext, '\n==+ *([^=]-) *==+' ) do local section = string.match( wikitext, '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = string.match( wikitext, '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = string.gsub( wikitext, nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because unlike all other tags, both opening and closing <section> tags are self-closing. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = string.match( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in string.gmatch( wikitext, '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' wikitext = string.gsub( wikitext, '<!%-%-.-%-%->', '' ) -- remove comments wikitext = string.gsub( wikitext, '\n%[%b[]%]\n', '\n' ) -- remove files and categories wikitext = string.gsub( wikitext, '\n(%b{}) *(%b{}) *\n', '\n%1\n%2\n' ) -- separate neighboring tables and block templates wikitext = string.gsub( wikitext, '\n%b{} *\n', '\n%0\n' ) -- add spacing between tables and block templates wikitext = string.gsub( wikitext, '\n%b{} *\n', '\n' ) -- remove tables and block templates wikitext = string.gsub( wikitext, '\n[*#][^\n]*', '\n' ) -- remove lists wikitext = string.gsub( wikitext, '\n==+[^=]+==+ *\n', '\n' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( template, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return string.match( templateWikitext, '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = string.match( templateWikitext, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '%[%b[]%]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = string.gsub( value, '@@_@@', '=' ) value = string.gsub( value, '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd -- Don't match closing tags like </div>, comments like <!--foo-->, comparisons like 1<2 or things like <3 for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/!%d].->)' ) do tagName = WikitextParser.getTagName( tagOpen ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're probably in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) if tagEnd then tag = string.sub( wikitext, tagStart, tagEnd - 1 ) -- If no end tag is found, assume we matched something that wasn't a tag, like <no. 1> else tag = nil end end table.insert( tags, tag ) end return tags end -- Get the name of the tag in the given wikitext -- @param tag Required. Tag to parse. -- @return Name of the tag or nil if not found function WikitextParser.getTagName( tagWikitext ) local tagName = string.match( tagWikitext, '^< *(.-)[ />]' ) if tagName then tagName = string.lower( tagName ) end return tagName end -- Get the value of an attribute in the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function WikitextParser.getTagAttribute( tagWikitext, attribute ) local _quote, value = string.match( tagWikitext, '^<[^/>]*' .. attribute .. ' *= *(["\']?)([^/>]-)%1[ />]' ) return value end -- Get the content of the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @return Content of the tag. May be empty if the tag is empty. Will be nil if the tag is self-closing. -- @todo May fail with nested tags function WikitextParser.getTagContent( tagWikitext ) return string.match( tagWikitext, '^<.->.-</.->' ) end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the reference with the given name from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param referenceName Required. Name of the reference. -- @return Wikitext of the reference function WikitextParser.getReference( wikitext, referenceName ) local references = WikitextParser.getReferences( wikitext ) for _, reference in pairs( references ) do local content = WikitextParser.getTagContent( reference ) local name = WikitextParser.getTagAttribute( reference, 'name' ) if content and name == referenceName then return reference end end end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in string.gmatch( wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function WikitextParser.getTableAttribute( tableWikitext, attribute ) local _quote, value = string.match( tableWikitext, '^{|[^\n]*' .. attribute .. ' *= *(["\']?)([^\n]-)%1[^\n]*\n' ) return value end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTable( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableAttribute( t, 'id' ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return string.match( fileWikitext, '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%b[]' ) do if string.match( link, '^%[//' ) or string.match( link, '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser gl3n4krdkr4sfrd4istee98ixrolxlt 797553 797552 2025-10-21T12:42:50Z en>Sophivorus 0 Simplify and improve getParagraphs by using frontier patterns 797553 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = string.gsub( wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in string.gmatch( wikitext, '\n==+ *([^=]-) *==+' ) do local section = string.match( wikitext, '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = string.match( wikitext, '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = string.gsub( wikitext, nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because unlike all other tags, both opening and closing <section> tags are self-closing. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) wikitext = string.match( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) if wikitext then return mw.text.trim( wikitext ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in string.gmatch( wikitext, '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' -- add newlines to simplfy patterns wikitext = string.gsub( wikitext, '%f[^\n]<!%-%-.-%-%->%f[\n]', '' ) -- remove comments wikitext = string.gsub( wikitext, '%f[^\n]%[%b[]%]%f[\n]', '' ) -- remove files and categories wikitext = string.gsub( wikitext, '%f[^\n]%b{} *%f[\n]', '' ) -- remove tables and block templates wikitext = string.gsub( wikitext, '%f[^\n](%b{}) *(%b{}) *%f[\n]', '' ) -- remove neighboring tables and block templates wikitext = string.gsub( wikitext, '%f[^\n](%b{}) *<!%-%-.-%-%-> *(%b{}) *%f[\n]', '' ) -- remove neighboring tables and block templates with a comment among them wikitext = string.gsub( wikitext, '%f[^\n][*#].-%f[\n]', '' ) -- remove lists wikitext = string.gsub( wikitext, '%f[^\n]==+[^=]+==+ *%f[\n]', '' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( template, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return string.match( templateWikitext, '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = string.match( templateWikitext, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '%[%b[]%]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = string.gsub( value, '@@_@@', '=' ) value = string.gsub( value, '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd -- Don't match closing tags like </div>, comments like <!--foo-->, comparisons like 1<2 or things like <3 for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/!%d].->)' ) do tagName = WikitextParser.getTagName( tagOpen ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're probably in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) if tagEnd then tag = string.sub( wikitext, tagStart, tagEnd - 1 ) -- If no end tag is found, assume we matched something that wasn't a tag, like <no. 1> else tag = nil end end table.insert( tags, tag ) end return tags end -- Get the name of the tag in the given wikitext -- @param tag Required. Tag to parse. -- @return Name of the tag or nil if not found function WikitextParser.getTagName( tagWikitext ) local tagName = string.match( tagWikitext, '^< *(.-)[ />]' ) if tagName then tagName = string.lower( tagName ) end return tagName end -- Get the value of an attribute in the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function WikitextParser.getTagAttribute( tagWikitext, attribute ) local _quote, value = string.match( tagWikitext, '^<[^/>]*' .. attribute .. ' *= *(["\']?)([^/>]-)%1[ />]' ) return value end -- Get the content of the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @return Content of the tag. May be empty if the tag is empty. Will be nil if the tag is self-closing. -- @todo May fail with nested tags function WikitextParser.getTagContent( tagWikitext ) return string.match( tagWikitext, '^<.->.-</.->' ) end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the reference with the given name from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param referenceName Required. Name of the reference. -- @return Wikitext of the reference function WikitextParser.getReference( wikitext, referenceName ) local references = WikitextParser.getReferences( wikitext ) for _, reference in pairs( references ) do local content = WikitextParser.getTagContent( reference ) local name = WikitextParser.getTagAttribute( reference, 'name' ) if content and name == referenceName then return reference end end end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in string.gmatch( wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function WikitextParser.getTableAttribute( tableWikitext, attribute ) local _quote, value = string.match( tableWikitext, '^{|[^\n]*' .. attribute .. ' *= *(["\']?)([^\n]-)%1[^\n]*\n' ) return value end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTable( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableAttribute( t, 'id' ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return string.match( fileWikitext, '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%b[]' ) do if string.match( link, '^%[//' ) or string.match( link, '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser rs9tzr0m21sihr2maqoyitxah6ls278 797554 797553 2025-11-03T12:54:38Z en>Sophivorus 0 Support repeated section tags 797554 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = string.gsub( wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in string.gmatch( wikitext, '\n==+ *([^=]-) *==+' ) do local section = string.match( wikitext, '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level, wikitext = string.match( wikitext, '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = string.gsub( wikitext, nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because unlike all other tags, both opening and closing <section> tags are self-closing. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) local sections = {} for section in string.gmatch( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) do table.insert( sections, section ) end if #sections > 0 then return table.concat( sections ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in string.gmatch( wikitext, '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' -- add newlines to simplfy patterns wikitext = string.gsub( wikitext, '%f[^\n]<!%-%-.-%-%->%f[\n]', '' ) -- remove comments wikitext = string.gsub( wikitext, '%f[^\n]%[%b[]%]%f[\n]', '' ) -- remove files and categories wikitext = string.gsub( wikitext, '%f[^\n]%b{} *%f[\n]', '' ) -- remove tables and block templates wikitext = string.gsub( wikitext, '%f[^\n]%b{} *%b{} *%f[\n]', '' ) -- remove neighboring tables and block templates wikitext = string.gsub( wikitext, '%f[^\n]%b{} *<!%-%-.-%-%-> *%b{} *%f[\n]', '' ) -- remove neighboring tables and block templates with a comment among them wikitext = string.gsub( wikitext, '%f[^\n][*#].-%f[\n]', '' ) -- remove lists wikitext = string.gsub( wikitext, '%f[^\n]==+[^=]+==+ *%f[\n]', '' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( template, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return string.match( templateWikitext, '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = string.match( templateWikitext, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '%[%b[]%]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = string.gsub( value, '@@_@@', '=' ) value = string.gsub( value, '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd -- Don't match closing tags like </div>, comments like <!--foo-->, comparisons like 1<2 or things like <3 for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/!%d].->)' ) do tagName = WikitextParser.getTagName( tagOpen ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're probably in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) if tagEnd then tag = string.sub( wikitext, tagStart, tagEnd - 1 ) -- If no end tag is found, assume we matched something that wasn't a tag, like <no. 1> else tag = nil end end table.insert( tags, tag ) end return tags end -- Get the name of the tag in the given wikitext -- @param tag Required. Tag to parse. -- @return Name of the tag or nil if not found function WikitextParser.getTagName( tagWikitext ) local tagName = string.match( tagWikitext, '^< *(.-)[ />]' ) if tagName then tagName = string.lower( tagName ) end return tagName end -- Get the value of an attribute in the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function WikitextParser.getTagAttribute( tagWikitext, attribute ) local _quote, value = string.match( tagWikitext, '^<[^/>]*' .. attribute .. ' *= *(["\']?)([^/>]-)%1[ />]' ) return value end -- Get the content of the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @return Content of the tag. May be empty if the tag is empty. Will be nil if the tag is self-closing. -- @todo May fail with nested tags function WikitextParser.getTagContent( tagWikitext ) return string.match( tagWikitext, '^<.->.-</.->' ) end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the reference with the given name from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param referenceName Required. Name of the reference. -- @return Wikitext of the reference function WikitextParser.getReference( wikitext, referenceName ) local references = WikitextParser.getReferences( wikitext ) for _, reference in pairs( references ) do local content = WikitextParser.getTagContent( reference ) local name = WikitextParser.getTagAttribute( reference, 'name' ) if content and name == referenceName then return reference end end end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in string.gmatch( wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function WikitextParser.getTableAttribute( tableWikitext, attribute ) local _quote, value = string.match( tableWikitext, '^{|[^\n]*' .. attribute .. ' *= *(["\']?)([^\n]-)%1[^\n]*\n' ) return value end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTable( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableAttribute( t, 'id' ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return string.match( fileWikitext, '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%b[]' ) do if string.match( link, '^%[//' ) or string.match( link, '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser h5pgr9hqwjcsn2eqsnegqry14h34fqq 797555 797554 2026-01-17T13:40:07Z en>Sophivorus 0 Avoid Lua linting errors 797555 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local WikitextParser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function WikitextParser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = string.gsub( wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function WikitextParser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in string.gmatch( wikitext, '\n==+ *([^=]-) *==+' ) do local section = string.match( wikitext, '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function WikitextParser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level level, wikitext = string.match( wikitext, '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = string.gsub( wikitext, nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because unlike all other tags, both opening and closing <section> tags are self-closing. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function WikitextParser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) local sections = {} for section in string.gmatch( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) do table.insert( sections, section ) end if #sections > 0 then return table.concat( sections ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function WikitextParser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in string.gmatch( wikitext, '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function WikitextParser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' -- add newlines to simplfy patterns wikitext = string.gsub( wikitext, '%f[^\n]<!%-%-.-%-%->%f[\n]', '' ) -- remove comments wikitext = string.gsub( wikitext, '%f[^\n]%[%b[]%]%f[\n]', '' ) -- remove files and categories wikitext = string.gsub( wikitext, '%f[^\n]%b{} *%f[\n]', '' ) -- remove tables and block templates wikitext = string.gsub( wikitext, '%f[^\n]%b{} *%b{} *%f[\n]', '' ) -- remove neighboring tables and block templates wikitext = string.gsub( wikitext, '%f[^\n]%b{} *<!%-%-.-%-%-> *%b{} *%f[\n]', '' ) -- remove neighboring tables and block templates with a comment among them wikitext = string.gsub( wikitext, '%f[^\n][*#].-%f[\n]', '' ) -- remove lists wikitext = string.gsub( wikitext, '%f[^\n]==+[^=]+==+ *%f[\n]', '' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function WikitextParser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( template, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function WikitextParser.getTemplate( wikitext, name ) local templates = WikitextParser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = WikitextParser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function WikitextParser.getTemplateName( templateWikitext ) return string.match( templateWikitext, '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function WikitextParser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = string.match( templateWikitext, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '%[%b[]%]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = string.gsub( value, '@@_@@', '=' ) value = string.gsub( value, '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function WikitextParser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd -- Don't match closing tags like </div>, comments like <!--foo-->, comparisons like 1<2 or things like <3 for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/!%d].->)' ) do tagName = WikitextParser.getTagName( tagOpen ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're probably in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) if tagEnd then tag = string.sub( wikitext, tagStart, tagEnd - 1 ) -- If no end tag is found, assume we matched something that wasn't a tag, like <no. 1> else tag = nil end end table.insert( tags, tag ) end return tags end -- Get the name of the tag in the given wikitext -- @param tag Required. Tag to parse. -- @return Name of the tag or nil if not found function WikitextParser.getTagName( tagWikitext ) local tagName = string.match( tagWikitext, '^< *(.-)[ />]' ) if tagName then tagName = string.lower( tagName ) end return tagName end -- Get the value of an attribute in the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function WikitextParser.getTagAttribute( tagWikitext, attribute ) local _, value = string.match( tagWikitext, '^<[^/>]*' .. attribute .. ' *= *(["\']?)([^/>]-)%1[ />]' ) return value end -- Get the content of the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @return Content of the tag. May be empty if the tag is empty. Will be nil if the tag is self-closing. -- @todo May fail with nested tags function WikitextParser.getTagContent( tagWikitext ) return string.match( tagWikitext, '^<.->.-</.->' ) end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function WikitextParser.getGalleries( wikitext ) local galleries = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function WikitextParser.getReferences( wikitext ) local references = {} local tags = WikitextParser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = WikitextParser.getTagName( tag ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the reference with the given name from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param referenceName Required. Name of the reference. -- @return Wikitext of the reference function WikitextParser.getReference( wikitext, referenceName ) local references = WikitextParser.getReferences( wikitext ) for _, reference in pairs( references ) do local content = WikitextParser.getTagContent( reference ) local name = WikitextParser.getTagAttribute( reference, 'name' ) if content and name == referenceName then return reference end end end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function WikitextParser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in string.gmatch( wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function WikitextParser.getTableAttribute( tableWikitext, attribute ) local _, value = string.match( tableWikitext, '^{|[^\n]*' .. attribute .. ' *= *(["\']?)([^\n]-)%1[^\n]*\n' ) return value end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function WikitextParser.getTable( wikitext, id ) local tables = WikitextParser.getTables( wikitext ) for _, t in pairs( tables ) do if id == WikitextParser.getTableAttribute( t, 'id' ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function WikitextParser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function WikitextParser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function WikitextParser.getFiles( wikitext ) local files = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function WikitextParser.getFileName( fileWikitext ) return string.match( fileWikitext, '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function WikitextParser.getCategories( wikitext ) local categories = {} local links = WikitextParser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function WikitextParser.getExternalLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%b[]' ) do if string.match( link, '^%[//' ) or string.match( link, '^%[https?://' ) then table.insert( links, link ) end end return links end return WikitextParser cg08iqs1angnfozr3nne9xyalcy0zao 797556 797555 2026-02-11T14:45:12Z en>Sophivorus 0 Rename variable "WikitextParser" to just "parser" for simplicty, and fix a few edge cases 797556 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local parser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function parser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = string.gsub( wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function parser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in string.gmatch( wikitext, '\n==+ *([^=]-) *==+' ) do local section = string.match( wikitext, '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function parser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level level, wikitext = string.match( wikitext, '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = string.gsub( wikitext, nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because unlike all other tags, both opening and closing <section> tags are self-closing. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function parser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) local sections = {} for section in string.gmatch( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) do table.insert( sections, section ) end if #sections > 0 then return table.concat( sections ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function parser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in string.gmatch( wikitext, '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function parser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' -- add newlines to simplfy patterns wikitext = string.gsub( wikitext, '%f[^\n]<!%-%-.-%-%->%f[\n]', '' ) -- remove comments wikitext = string.gsub( wikitext, '%f[^\n]%[%b[]%]%f[\n]', '' ) -- remove files and categories wikitext = string.gsub( wikitext, '%f[^\n]%b{} *%f[\n]', '' ) -- remove tables and block templates wikitext = string.gsub( wikitext, '%f[^\n]%b{} *%b{} *%f[\n]', '' ) -- remove neighboring tables and block templates wikitext = string.gsub( wikitext, '%f[^\n]%b{} *<!%-%-.-%-%-> *%b{} *%f[\n]', '' ) -- remove neighboring tables and block templates with a comment among them wikitext = string.gsub( wikitext, '%f[^\n][*#].-%f[\n]', '' ) -- remove lists wikitext = string.gsub( wikitext, '%f[^\n]==+[^=]+==+ *%f[\n]', '' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function parser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( template, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function parser.getTemplate( wikitext, name ) local templates = parser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = parser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function parser.getTemplateName( templateWikitext ) return string.match( templateWikitext, '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function parser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = string.match( templateWikitext, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '%[%b[]%]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if tonumber( name ) then name = tonumber( name ) end if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = string.gsub( value, '@@_@@', '=' ) value = string.gsub( value, '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function parser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd -- Don't match closing tags like </div>, comments like <!--foo-->, comparisons like 1<2 or things like <3 for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/!%d].->)' ) do tagName = parser.getTagName( tagOpen ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're probably in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) if tagEnd then tag = string.sub( wikitext, tagStart, tagEnd - 1 ) -- If no end tag is found, assume we matched something that wasn't a tag, like <no. 1> else tag = nil end end table.insert( tags, tag ) end return tags end -- Get the name of the tag in the given wikitext -- @param tag Required. Tag to parse. -- @return Name of the tag or nil if not found function parser.getTagName( tagWikitext ) local tagName = string.match( tagWikitext, '^< *(.-)[ />]' ) if tagName then tagName = string.lower( tagName ) end return tagName end -- Get the value of an attribute in the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function parser.getTagAttribute( tagWikitext, attribute ) local _, value = string.match( tagWikitext, '^<[^/>]*' .. attribute .. ' *= *(["\']?)([^/>]-)%1[ />]' ) return value end -- Get the content of the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @return Content of the tag. May be empty if the tag is empty. Will be nil if the tag is self-closing. -- @todo May fail with nested tags function parser.getTagContent( tagWikitext ) return string.match( tagWikitext, '^<.->.-</.->' ) end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function parser.getGalleries( wikitext ) local galleries = {} local tags = parser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = parser.getTagName( tag ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function parser.getReferences( wikitext ) local references = {} local tags = parser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = parser.getTagName( tag ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the reference with the given name from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param referenceName Required. Name of the reference. -- @return Wikitext of the reference function parser.getReference( wikitext, referenceName ) local references = parser.getReferences( wikitext ) for _, reference in pairs( references ) do local content = parser.getTagContent( reference ) local name = parser.getTagAttribute( reference, 'name' ) if content and name == referenceName then return reference end end end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function parser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in string.gmatch( wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function parser.getTableAttribute( tableWikitext, attribute ) local _, value = string.match( tableWikitext, '^{|[^\n]*' .. attribute .. ' *= *(["\']?)([^\n]-)%1[^\n]*\n' ) if not value or value == '' then value = string.match( tableWikitext, '^{|[^\n]*' .. attribute .. ' *= *([^\n ]+)[^\n]*\n' ) end return value end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function parser.getTable( wikitext, id ) local tables = parser.getTables( wikitext ) for _, t in pairs( tables ) do if id == parser.getTableAttribute( t, 'id' ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function parser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function parser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function parser.getFiles( wikitext ) local files = {} local links = parser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function parser.getFileName( fileWikitext ) return string.match( fileWikitext, '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function parser.getCategories( wikitext ) local categories = {} local links = parser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function parser.getExternalLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%b[]' ) do if string.match( link, '^%[//' ) or string.match( link, '^%[https?://' ) then table.insert( links, link ) end end return links end return parser m6egne6d445gid50doxwuxuw8b4hok5 797557 797556 2026-06-08T20:27:21Z SM7 3953 191 revisions imported from [[:en:Module:WikitextParser]] 797556 Scribunto text/plain -- Module:WikitextParser is a general-purpose wikitext parser -- Documentation and master version: https://en.wikipedia.org/wiki/Module:WikitextParser -- Authors: User:Sophivorus, User:Certes, User:Aidan9382, et al. -- License: CC-BY-SA-4.0 local parser = {} -- Private helper method to escape a string for use in regexes local function escapeString( str ) return string.gsub( str, '[%^%$%(%)%.%[%]%*%+%-%?%%]', '%%%0' ) end -- Get the lead section from the given wikitext -- The lead section is any content before the first section title. -- @param wikitext Required. Wikitext to parse. -- @return Wikitext of the lead section. May be empty if the lead section is empty. function parser.getLead( wikitext ) wikitext = '\n' .. wikitext wikitext = string.gsub( wikitext, '\n==.*', '' ) wikitext = mw.text.trim( wikitext ) return wikitext end -- Get the sections from the given wikitext -- This method doesn't get the lead section, use getLead for that -- @param wikitext Required. Wikitext to parse. -- @return Map from section title to section content function parser.getSections( wikitext ) local sections = {} wikitext = '\n' .. wikitext .. '\n==' for title in string.gmatch( wikitext, '\n==+ *([^=]-) *==+' ) do local section = string.match( wikitext, '\n==+ *' .. escapeString( title ) .. ' *==+(.-)\n==' ) section = mw.text.trim( section ) sections[ title ] = section end return sections end -- Get a section from the given wikitext (including any subsections) -- If the given section title appears more than once, only the section of the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param title Required. Title of the section -- @return Wikitext of the section, or nil if it isn't found. May be empty if the section is empty or contains only subsections. function parser.getSection( wikitext, title ) title = mw.text.trim( title ) title = escapeString( title ) wikitext = '\n' .. wikitext .. '\n' local level level, wikitext = string.match( wikitext, '\n(==+) *' .. title .. ' *==.-\n(.*)' ) if wikitext then local nextSection = '\n==' .. string.rep( '=?', #level - 2 ) .. '[^=].*' wikitext = string.gsub( wikitext, nextSection, '' ) -- remove later sections at this level or higher wikitext = mw.text.trim( wikitext ) return wikitext end end -- Get the content of a <section> tag from the given wikitext. -- We can't use getTags because unlike all other tags, both opening and closing <section> tags are self-closing. -- @param wikitext Required. Wikitext to parse. -- @param name Required. Name of the <section> tag -- @return Content of the <section> tag, or nil if it isn't found. May be empty if the section tag is empty. function parser.getSectionTag( wikitext, name ) name = mw.text.trim( name ) name = escapeString( name ) local sections = {} for section in string.gmatch( wikitext, '< *section +begin *= *["\']? *' .. name .. ' *["\']? */>(.-)< *section +end= *["\']? *'.. name ..' *["\']? */>' ) do table.insert( sections, section ) end if #sections > 0 then return table.concat( sections ) end end -- Get the lists from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of lists. function parser.getLists( wikitext ) local lists = {} wikitext = '\n' .. wikitext .. '\n\n' for list in string.gmatch( wikitext, '\n([*#].-)\n[^*#]' ) do table.insert( lists, list ) end return lists end -- Get the paragraphs from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of paragraphs. function parser.getParagraphs( wikitext ) local paragraphs = {} -- Remove non-paragraphs wikitext = '\n' .. wikitext .. '\n' -- add newlines to simplfy patterns wikitext = string.gsub( wikitext, '%f[^\n]<!%-%-.-%-%->%f[\n]', '' ) -- remove comments wikitext = string.gsub( wikitext, '%f[^\n]%[%b[]%]%f[\n]', '' ) -- remove files and categories wikitext = string.gsub( wikitext, '%f[^\n]%b{} *%f[\n]', '' ) -- remove tables and block templates wikitext = string.gsub( wikitext, '%f[^\n]%b{} *%b{} *%f[\n]', '' ) -- remove neighboring tables and block templates wikitext = string.gsub( wikitext, '%f[^\n]%b{} *<!%-%-.-%-%-> *%b{} *%f[\n]', '' ) -- remove neighboring tables and block templates with a comment among them wikitext = string.gsub( wikitext, '%f[^\n][*#].-%f[\n]', '' ) -- remove lists wikitext = string.gsub( wikitext, '%f[^\n]==+[^=]+==+ *%f[\n]', '' ) -- remove section titles wikitext = mw.text.trim( wikitext ) for paragraph in mw.text.gsplit( wikitext, '\n\n+' ) do if mw.text.trim( paragraph ) ~= '' then table.insert( paragraphs, paragraph ) end end return paragraphs end -- Get the templates from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of templates. function parser.getTemplates( wikitext ) local templates = {} for template in string.gmatch( wikitext, '{%b{}}' ) do if string.sub( template, 1, 3 ) ~= '{{#' then -- skip parser functions like #if table.insert( templates, template ) end end return templates end -- Get the requested template from the given wikitext. -- If the template appears more than once, only the first instance will be returned -- @param wikitext Required. Wikitext to parse. -- @param name Name of the template to get -- @return Wikitext of the template, or nil if it wasn't found function parser.getTemplate( wikitext, name ) local templates = parser.getTemplates( wikitext ) local lang = mw.language.getContentLanguage() for _, template in pairs( templates ) do local templateName = parser.getTemplateName( template ) if lang:ucfirst( templateName ) == lang:ucfirst( name ) then return template end end end -- Get name of the template from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Name of the template -- @todo Strip "Template:" namespace? function parser.getTemplateName( templateWikitext ) return string.match( templateWikitext, '^{{ *([^}|\n]+)' ) end -- Get the parameters from the given template wikitext. -- @param templateWikitext Required. Wikitext of the template to parse. -- @return Map from parameter names to parameter values, NOT IN THE ORIGINAL ORDER. -- @return Order in which the parameters were parsed. function parser.getTemplateParameters( templateWikitext ) local parameters = {} local paramOrder = {} local params = string.match( templateWikitext, '{{[^|}]-|(.*)}}' ) if params then -- Temporarily replace pipes in subtemplates and links to avoid chaos for subtemplate in string.gmatch( params, '{%b{}}' ) do params = string.gsub( params, escapeString( subtemplate ), string.gsub( subtemplate, '.', { ['%']='%%', ['|']="@@:@@", ['=']='@@_@@' } ) ) end for link in string.gmatch( params, '%[%b[]%]' ) do params = string.gsub( params, escapeString( link ), string.gsub( link, '.', { ['%']='%%', ['|']='@@:@@', ['=']='@@_@@' } ) ) end local count = 0 local parts, name, value for param in mw.text.gsplit( params, '|' ) do parts = mw.text.split( param, '=' ) name = mw.text.trim( parts[1] ) if tonumber( name ) then name = tonumber( name ) end if #parts == 1 then value = name count = count + 1 name = count else value = table.concat( parts, '=', 2 ); value = mw.text.trim( value ) end value = string.gsub( value, '@@_@@', '=' ) value = string.gsub( value, '@@:@@', '|' ) parameters[ name ] = value table.insert( paramOrder, name ) end end return parameters, paramOrder end -- Get the tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tags. function parser.getTags( wikitext ) local tags = {} local tag, tagName, tagEnd -- Don't match closing tags like </div>, comments like <!--foo-->, comparisons like 1<2 or things like <3 for tagStart, tagOpen in string.gmatch( wikitext, '()(<[^/!%d].->)' ) do tagName = parser.getTagName( tagOpen ) -- If we're in a self-closing tag, like <ref name="foo" />, <references/>, <br/>, <br>, <hr>, etc. if string.match( tagOpen, '<.-/>' ) or tagName == 'br' or tagName == 'hr' then tag = tagOpen -- If we're in a tag that may contain others like it, like <div> or <span> elseif tagName == 'div' or tagName == 'span' then local position = tagStart + #tagOpen - 1 local depth = 1 while depth > 0 do tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', position ) if tagEnd then tagEnd = tagEnd - 1 else break -- unclosed tag end position = string.match( wikitext, '()< ?' .. tagName .. '[ >]', position + 1 ) if not position then position = tagEnd + 1 end if position > tagEnd then depth = depth - 1 else depth = depth + 1 end end tag = string.sub( wikitext, tagStart, tagEnd ) -- Else we're probably in tag that shouldn't contain others like it, like <math> or <strong> else tagEnd = string.match( wikitext, '</ ?' .. tagName .. ' ?>()', tagStart ) if tagEnd then tag = string.sub( wikitext, tagStart, tagEnd - 1 ) -- If no end tag is found, assume we matched something that wasn't a tag, like <no. 1> else tag = nil end end table.insert( tags, tag ) end return tags end -- Get the name of the tag in the given wikitext -- @param tag Required. Tag to parse. -- @return Name of the tag or nil if not found function parser.getTagName( tagWikitext ) local tagName = string.match( tagWikitext, '^< *(.-)[ />]' ) if tagName then tagName = string.lower( tagName ) end return tagName end -- Get the value of an attribute in the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function parser.getTagAttribute( tagWikitext, attribute ) local _, value = string.match( tagWikitext, '^<[^/>]*' .. attribute .. ' *= *(["\']?)([^/>]-)%1[ />]' ) return value end -- Get the content of the given tag. -- @param tagWikitext Required. Wikitext of the tag to parse. -- @return Content of the tag. May be empty if the tag is empty. Will be nil if the tag is self-closing. -- @todo May fail with nested tags function parser.getTagContent( tagWikitext ) return string.match( tagWikitext, '^<.->.-</.->' ) end -- Get the <gallery> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of gallery tags. function parser.getGalleries( wikitext ) local galleries = {} local tags = parser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = parser.getTagName( tag ) if tagName == 'gallery' then table.insert( galleries, tag ) end end return galleries end -- Get the <ref> tags from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of ref tags. function parser.getReferences( wikitext ) local references = {} local tags = parser.getTags( wikitext ) for _, tag in pairs( tags ) do local tagName = parser.getTagName( tag ) if tagName == 'ref' then table.insert( references, tag ) end end return references end -- Get the reference with the given name from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @param referenceName Required. Name of the reference. -- @return Wikitext of the reference function parser.getReference( wikitext, referenceName ) local references = parser.getReferences( wikitext ) for _, reference in pairs( references ) do local content = parser.getTagContent( reference ) local name = parser.getTagAttribute( reference, 'name' ) if content and name == referenceName then return reference end end end -- Get the tables from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of tables. function parser.getTables( wikitext ) local tables = {} wikitext = '\n' .. wikitext for t in string.gmatch( wikitext, '\n%b{}' ) do if string.sub( t, 1, 3 ) == '\n{|' then t = mw.text.trim( t ) -- exclude the leading newline table.insert( tables, t ) end end return tables end -- Get the id from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @param attribute Required. Name of the attribute. -- @return Value of the attribute or nil if not found function parser.getTableAttribute( tableWikitext, attribute ) local _, value = string.match( tableWikitext, '^{|[^\n]*' .. attribute .. ' *= *(["\']?)([^\n]-)%1[^\n]*\n' ) if not value or value == '' then value = string.match( tableWikitext, '^{|[^\n]*' .. attribute .. ' *= *([^\n ]+)[^\n]*\n' ) end return value end -- Get a table by id from the given wikitext -- @param wikitext Required. Wikitext to parse. -- @param id Required. Id of the table -- @return Wikitext of the table or nil if not found function parser.getTable( wikitext, id ) local tables = parser.getTables( wikitext ) for _, t in pairs( tables ) do if id == parser.getTableAttribute( t, 'id' ) then return t end end end -- Get the data from the given table wikitext -- @param tableWikitext Required. Wikitext of the table to parse. -- @return Table data -- @todo Test and make more robust function parser.getTableData( tableWikitext ) local tableData = {} tableWikitext = mw.text.trim( tableWikitext ); tableWikitext = string.gsub( tableWikitext, '^{|.-\n', '' ) -- remove the header tableWikitext = string.gsub( tableWikitext, '\n|}$', '' ) -- remove the footer tableWikitext = string.gsub( tableWikitext, '^|%+.-\n', '' ) -- remove any caption tableWikitext = string.gsub( tableWikitext, '|%-.-\n', '|-\n' ) -- remove any row attributes tableWikitext = string.gsub( tableWikitext, '^|%-\n', '' ) -- remove any leading empty row tableWikitext = string.gsub( tableWikitext, '\n|%-$', '' ) -- remove any trailing empty row for rowWikitext in mw.text.gsplit( tableWikitext, '|-', true ) do local rowData = {} rowWikitext = string.gsub( rowWikitext, '||', '\n|' ) rowWikitext = string.gsub( rowWikitext, '!!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '\n!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^!', '\n|' ) rowWikitext = string.gsub( rowWikitext, '^\n|', '' ) for cellWikitext in mw.text.gsplit( rowWikitext, '\n|' ) do cellWikitext = mw.text.trim( cellWikitext ) table.insert( rowData, cellWikitext ) end table.insert( tableData, rowData ) end return tableData end -- Get the internal links from the given wikitext (includes category and file links). -- @param wikitext Required. Wikitext to parse. -- @return Sequence of internal links. function parser.getLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%[%b[]%]' ) do table.insert( links, link ) end return links end -- Get the file links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of file links. function parser.getFiles( wikitext ) local files = {} local links = parser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '^%[%[ *(.-) *:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'File' then table.insert( files, link ) end end return files end -- Get name of the file from the given file wikitext. -- @param fileWikitext Required. Wikitext of the file to parse. -- @return Name of the file function parser.getFileName( fileWikitext ) return string.match( fileWikitext, '^%[%[ *.- *: *(.-) *[]|]' ) end -- Get the category links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of category links. function parser.getCategories( wikitext ) local categories = {} local links = parser.getLinks( wikitext ) for _, link in pairs( links ) do local namespace = string.match( link, '^%[%[ -(.-) -:' ) if namespace and mw.site.namespaces[ namespace ] and mw.site.namespaces[ namespace ].canonicalName == 'Category' then table.insert( categories, link ) end end return categories end -- Get the external links from the given wikitext. -- @param wikitext Required. Wikitext to parse. -- @return Sequence of external links. function parser.getExternalLinks( wikitext ) local links = {} for link in string.gmatch( wikitext, '%b[]' ) do if string.match( link, '^%[//' ) or string.match( link, '^%[https?://' ) then table.insert( links, link ) end end return links end return parser m6egne6d445gid50doxwuxuw8b4hok5 टेम्पलेट:Avoid wrap 10 101022 797570 2019-01-02T14:59:14Z en>TheDJ 0 experiment with avoid wrap 797570 wikitext text/x-wiki <span class="avoidwrap" style="display:inline-block;">{{{1}}}</span><noinclude> {{documentation}} <!-- Categories go on the /doc page; interwikis go to Wikidata. --> </noinclude> t0fisb4vbw5b9iv4j2xygm8zyci9s0r 797571 797570 2022-03-25T04:30:52Z en>Toolsmasts 0 797571 wikitext text/x-wiki <span class="avoidwrap" style="display:inline-block;">{{{1}}}</span><includeonly>[[File:Wedgie.jpg]]<noinclude> {{documentation}} <!-- Categories go on the /doc page; interwikis go to Wikidata. --> </noinclude> e0sattd37i7ak5vt2fn2zod7zsyciwb 797572 797571 2022-03-25T04:37:51Z en>Spicy 0 Reverted edits by [[Special:Contribs/Toolsmasts|Toolsmasts]] ([[User talk:Toolsmasts|talk]]) to last version by TheDJ 797572 wikitext text/x-wiki <span class="avoidwrap" style="display:inline-block;">{{{1}}}</span><noinclude> {{documentation}} <!-- Categories go on the /doc page; interwikis go to Wikidata. --> </noinclude> t0fisb4vbw5b9iv4j2xygm8zyci9s0r 797573 797572 2023-01-22T18:00:08Z en>MusikBot II 0 Protected "[[Template:Avoid wrap]]": [[Wikipedia:High-risk templates|High-risk template or module]]: 253 transclusions ([[User:MusikBot II/TemplateProtector|more info]]) ([Edit=Require autoconfirmed or confirmed access] (indefinite)) 797572 wikitext text/x-wiki <span class="avoidwrap" style="display:inline-block;">{{{1}}}</span><noinclude> {{documentation}} <!-- Categories go on the /doc page; interwikis go to Wikidata. --> </noinclude> t0fisb4vbw5b9iv4j2xygm8zyci9s0r 797574 797573 2024-03-07T18:00:45Z en>MusikBot II 0 Changed protection settings for "[[Template:Avoid wrap]]": [[Wikipedia:High-risk templates|High-risk template or module]]: 2670 transclusions ([[User:MusikBot II/TemplateProtector|more info]]) ([Edit=Require extended confirmed access] (indefinite) [Move=Require extended confirmed access] (indefinite)) 797572 wikitext text/x-wiki <span class="avoidwrap" style="display:inline-block;">{{{1}}}</span><noinclude> {{documentation}} <!-- Categories go on the /doc page; interwikis go to Wikidata. --> </noinclude> t0fisb4vbw5b9iv4j2xygm8zyci9s0r 797575 797574 2026-04-18T03:51:59Z en>Pppery 0 Changed protection settings for "[[Template:Avoid wrap]]" ([Edit=Require template editor access] (indefinite) [Move=Require template editor access] (indefinite)) 797572 wikitext text/x-wiki <span class="avoidwrap" style="display:inline-block;">{{{1}}}</span><noinclude> {{documentation}} <!-- Categories go on the /doc page; interwikis go to Wikidata. --> </noinclude> t0fisb4vbw5b9iv4j2xygm8zyci9s0r 797576 797575 2026-06-09T01:28:07Z SM7 3953 6 revisions imported from [[:en:Template:Avoid_wrap]] 797572 wikitext text/x-wiki <span class="avoidwrap" style="display:inline-block;">{{{1}}}</span><noinclude> {{documentation}} <!-- Categories go on the /doc page; interwikis go to Wikidata. --> </noinclude> t0fisb4vbw5b9iv4j2xygm8zyci9s0r टेम्पलेट:Avoid wrap/doc 10 101023 797577 2019-01-02T15:03:51Z en>TheDJ 0 docs 797577 wikitext text/x-wiki {{Documentation subpage}} <!-- Categories go where indicated at the bottom of this page, please; interwikis go to Wikidata (see also: [[Wikipedia:Wikidata]]) --> {{High-use| 996942 }} {{COinS safe|n}} {{tlx|Avoid wrap}} or {{tlx|avoidwrap}} avoids wrapping of specific text. To achieve the opposite effect of <code>&#123;&#123;Avoid wrap&#125;&#125;</code>, you can use {{tlx|wbr}}. For more information about wrapping and breaking sentences, see [[Wikipedia:Line-break handling]]. == Usage == :: <code><nowiki>{{Avoid wrap|these words stay together}}</nowiki></code> :: <code><nowiki>{{Avoid wrap|a, b, c, or d.}}</nowiki></code> :: <code><nowiki>{{Avoid wrap| merry-go-round }}</nowiki></code> :: <code><nowiki>{{Avoid wrap| [[4-part harmony]] }}</nowiki></code> :: <code><nowiki>{{Avoid wrap| [[Cascading Style Sheets|CSS]] }}</nowiki></code> :: <code><nowiki>{{Avoid wrap| [[#Examples|Examples section]] }}</nowiki></code> The template names {{tl|Nobr}} and {{tl|Nobreak}} are redirects that may be used instead of "{{tlc|Avoid wrap|…}}". === Examples === {| class=wikitable |"<code>They held <nowiki>{{Avoid wrap|10 kg (22 lb)}}</nowiki> in total.</code>" <br/> May render like this: :They held {{Avoid wrap|10 kg (22 lb)}} <br/>in total. <br/> Or like this: :They held <br/>10 kg (22 lb) in total. <br/> But ''not'' render like this: :They held 10 kg (22 <br/>lb) in total. | "<code>He encountered <nowiki>{{Avoid wrap|a tiger}}</nowiki> in the woods.</code>" <br/> May render like this: :He encountered a tiger <br/>in the woods. <br/> Or like this: :He encountered <br/>a tiger in the woods. <br/> But ''not'' like this: :He encountered a <br/>tiger in the woods. |} === Controlling line-breaking in infoboxes === This template may be used with {{tlx|Wbr}} and {{tlx|Spaces}} to control line-breaking in bulletless lists in infoboxes, to prevent wrapped long entries from being confused with multiple entries. See [[Template:Wbr/doc#Controlling line-breaking in infoboxes]] for details. === Handling equal-sign or bar === [[Help:Template|Templates]] have problems with parameter data that contains [[equal-sign]]s "<code>=</code>" or vertical bars "<code>|</code>" (pipes). Note that this does not apply if the equal-sign "<code>=</code>" or vertical bar "<code>|</code>" is inside a wikilink or another template. In the case that there is a bare equal-sign "<code>=</code>" or vertical bar "<code>|</code>", consider using {{tl|Avoid wrap begin}} + {{tl|Avoid wrap end}} instead. However, there are also other workarounds: For text that includes an equal-sign "=", precede the text with <code>1=</code>, use a triple-brace unnamed parameter <code><nowiki>{{{|=}}}</nowiki></code>, or the <code><nowiki>{{=}}</nowiki></code> template. For example: :<code><nowiki>{{Avoid wrap|</nowiki>1=2 + 2 = 4}}</code>, <br /><code><nowiki>{{Avoid wrap|2 + 2 {{{|=}}} 4}}</nowiki></code>, or <br /><code><nowiki>{{Avoid wrap|2 + 2 {{=}} 4}}</nowiki></code> which all render as this: :{{Avoid wrap|1=2 + 2 = 4}}. For text that includes a vertical bar "|", escape the bar(s) with "<code><nowiki>&amp;#124;</nowiki></code>" or "<code><nowiki>{{pipe}}</nowiki></code>" or "<code><nowiki>{{abs}}</nowiki></code>". For instance, put two bars "|6|" like this: :<code><nowiki>{{Avoid wrap|</nowiki>&amp;#124;6&amp;#124; < 7}}</code> &nbsp;or&nbsp; <code><nowiki>{{Avoid wrap|{{pipe}}6{{pipe}} < 7}}</nowiki></code> &nbsp;or&nbsp; <code><nowiki>{{avoid wrap|{{abs|6}} < 7}}</nowiki></code> Which renders this: :{{avoid wrap|&#124;6&#124; < 7}} == Technical details == The actual code that does the job is this [[HTML]] span tag that applies a class to the text inside the template: :{{code|lang=html|1=<span class="avoidwrap" style="display:inline-block;">This text will not wrap</span>}} The class <code>Avoid wrap</code> receives the CSS property {{code|lang=css|white-space: Avoid wrap;}} in [[MediaWiki:Common.css]]. Spaces at the beginning or end of the text will fall outside the no-wrap tag in the rendered text due to Wikimedia rendering mechanisms. The templates {{tl|nobr}} and {{tl|nobreak}} redirect here. == TemplateData == {{TemplateData header}} <templatedata>{ "description": "Prevents word wraps (line breaks) within text or inside a link which contains spaces or hyphens (-).", "params": { "1": { "label": "Text", "description": "Text or link to be protected.", "type": "string", "required": true } } }</templatedata> == See also == {{List of Avoid wrap-like templates}} <includeonly>{{#ifeq:{{SUBPAGENAME}}|sandbox|| <!-- Categories go below this line, please; interwikis go to Wikidata, thank you! --> [[Category:Line-handling templates]] }}</includeonly> 5nh6ea2duouth82yixfeeef8jingnoq 797578 797577 2019-01-02T15:07:58Z en>TheDJ 0 /* Technical details */ 797578 wikitext text/x-wiki {{Documentation subpage}} <!-- Categories go where indicated at the bottom of this page, please; interwikis go to Wikidata (see also: [[Wikipedia:Wikidata]]) --> {{High-use| 996942 }} {{COinS safe|n}} {{tlx|Avoid wrap}} or {{tlx|avoidwrap}} avoids wrapping of specific text. To achieve the opposite effect of <code>&#123;&#123;Avoid wrap&#125;&#125;</code>, you can use {{tlx|wbr}}. For more information about wrapping and breaking sentences, see [[Wikipedia:Line-break handling]]. == Usage == :: <code><nowiki>{{Avoid wrap|these words stay together}}</nowiki></code> :: <code><nowiki>{{Avoid wrap|a, b, c, or d.}}</nowiki></code> :: <code><nowiki>{{Avoid wrap| merry-go-round }}</nowiki></code> :: <code><nowiki>{{Avoid wrap| [[4-part harmony]] }}</nowiki></code> :: <code><nowiki>{{Avoid wrap| [[Cascading Style Sheets|CSS]] }}</nowiki></code> :: <code><nowiki>{{Avoid wrap| [[#Examples|Examples section]] }}</nowiki></code> The template names {{tl|Nobr}} and {{tl|Nobreak}} are redirects that may be used instead of "{{tlc|Avoid wrap|…}}". === Examples === {| class=wikitable |"<code>They held <nowiki>{{Avoid wrap|10 kg (22 lb)}}</nowiki> in total.</code>" <br/> May render like this: :They held {{Avoid wrap|10 kg (22 lb)}} <br/>in total. <br/> Or like this: :They held <br/>10 kg (22 lb) in total. <br/> But ''not'' render like this: :They held 10 kg (22 <br/>lb) in total. | "<code>He encountered <nowiki>{{Avoid wrap|a tiger}}</nowiki> in the woods.</code>" <br/> May render like this: :He encountered a tiger <br/>in the woods. <br/> Or like this: :He encountered <br/>a tiger in the woods. <br/> But ''not'' like this: :He encountered a <br/>tiger in the woods. |} === Controlling line-breaking in infoboxes === This template may be used with {{tlx|Wbr}} and {{tlx|Spaces}} to control line-breaking in bulletless lists in infoboxes, to prevent wrapped long entries from being confused with multiple entries. See [[Template:Wbr/doc#Controlling line-breaking in infoboxes]] for details. === Handling equal-sign or bar === [[Help:Template|Templates]] have problems with parameter data that contains [[equal-sign]]s "<code>=</code>" or vertical bars "<code>|</code>" (pipes). Note that this does not apply if the equal-sign "<code>=</code>" or vertical bar "<code>|</code>" is inside a wikilink or another template. In the case that there is a bare equal-sign "<code>=</code>" or vertical bar "<code>|</code>", consider using {{tl|Avoid wrap begin}} + {{tl|Avoid wrap end}} instead. However, there are also other workarounds: For text that includes an equal-sign "=", precede the text with <code>1=</code>, use a triple-brace unnamed parameter <code><nowiki>{{{|=}}}</nowiki></code>, or the <code><nowiki>{{=}}</nowiki></code> template. For example: :<code><nowiki>{{Avoid wrap|</nowiki>1=2 + 2 = 4}}</code>, <br /><code><nowiki>{{Avoid wrap|2 + 2 {{{|=}}} 4}}</nowiki></code>, or <br /><code><nowiki>{{Avoid wrap|2 + 2 {{=}} 4}}</nowiki></code> which all render as this: :{{Avoid wrap|1=2 + 2 = 4}}. For text that includes a vertical bar "|", escape the bar(s) with "<code><nowiki>&amp;#124;</nowiki></code>" or "<code><nowiki>{{pipe}}</nowiki></code>" or "<code><nowiki>{{abs}}</nowiki></code>". For instance, put two bars "|6|" like this: :<code><nowiki>{{Avoid wrap|</nowiki>&amp;#124;6&amp;#124; < 7}}</code> &nbsp;or&nbsp; <code><nowiki>{{Avoid wrap|{{pipe}}6{{pipe}} < 7}}</nowiki></code> &nbsp;or&nbsp; <code><nowiki>{{avoid wrap|{{abs|6}} < 7}}</nowiki></code> Which renders this: :{{avoid wrap|&#124;6&#124; < 7}} == Technical details == The actual code that does the job is this [[HTML]] span tag that applies a class to the text inside the template: :{{code|lang=html|1=<span class="avoidwrap" style="display:inline-block;">This text will not wrap</span>}} When there is not enough remaining space on a line to fit the contents of such as block, it will be pushed to the next line in its entirety. However when the block in itself doesn't fit on a single line, line breaking still applies to the 'internal' text block. This is especially advantageous for mobile rendering where the available line width might be much smaller than on desktops and might require line breaks to make longer words or sections fit. == TemplateData == {{TemplateData header}} <templatedata>{ "description": "Prevents word wraps (line breaks) within text or inside a link which contains spaces or hyphens (-).", "params": { "1": { "label": "Text", "description": "Text or link to be protected.", "type": "string", "required": true } } }</templatedata> == See also == {{List of Avoid wrap-like templates}} <includeonly>{{#ifeq:{{SUBPAGENAME}}|sandbox|| <!-- Categories go below this line, please; interwikis go to Wikidata, thank you! --> [[Category:Line-handling templates]] }}</includeonly> 2t7s8ug39h39rbfdzj5ej4kt6tgowel 797579 797578 2019-09-20T23:01:50Z en>Watchduck 0 797579 wikitext text/x-wiki {{Documentation subpage}} <!-- Categories go where indicated at the bottom of this page, please; interwikis go to Wikidata (see also: [[Wikipedia:Wikidata]]) --> {{High-use| 996942 }} {{COinS safe|n}} {{tlx|Avoid wrap}}, {{tlx|avoidwrap}} or {{tlx|awrap}} avoids wrapping of specific text. To achieve the opposite effect of <code>&#123;&#123;Avoid wrap&#125;&#125;</code>, you can use {{tlx|wbr}}. For more information about wrapping and breaking sentences, see [[Wikipedia:Line-break handling]]. == Usage == :: <code><nowiki>{{Avoid wrap|these words stay together}}</nowiki></code> :: <code><nowiki>{{Avoid wrap|a, b, c, or d.}}</nowiki></code> :: <code><nowiki>{{Avoid wrap| merry-go-round }}</nowiki></code> :: <code><nowiki>{{Avoid wrap| [[4-part harmony]] }}</nowiki></code> :: <code><nowiki>{{Avoid wrap| [[Cascading Style Sheets|CSS]] }}</nowiki></code> :: <code><nowiki>{{Avoid wrap| [[#Examples|Examples section]] }}</nowiki></code> The template names {{tl|Nobr}} and {{tl|Nobreak}} are redirects that may be used instead of "{{tlc|Avoid wrap|…}}". === Examples === {| class=wikitable |"<code>They held <nowiki>{{Avoid wrap|10 kg (22 lb)}}</nowiki> in total.</code>" <br/> May render like this: :They held {{Avoid wrap|10 kg (22 lb)}} <br/>in total. <br/> Or like this: :They held <br/>10 kg (22 lb) in total. <br/> But ''not'' render like this: :They held 10 kg (22 <br/>lb) in total. | "<code>He encountered <nowiki>{{Avoid wrap|a tiger}}</nowiki> in the woods.</code>" <br/> May render like this: :He encountered a tiger <br/>in the woods. <br/> Or like this: :He encountered <br/>a tiger in the woods. <br/> But ''not'' like this: :He encountered a <br/>tiger in the woods. |} === Controlling line-breaking in infoboxes === This template may be used with {{tlx|Wbr}} and {{tlx|Spaces}} to control line-breaking in bulletless lists in infoboxes, to prevent wrapped long entries from being confused with multiple entries. See [[Template:Wbr/doc#Controlling line-breaking in infoboxes]] for details. === Handling equal-sign or bar === [[Help:Template|Templates]] have problems with parameter data that contains [[equal-sign]]s "<code>=</code>" or vertical bars "<code>|</code>" (pipes). Note that this does not apply if the equal-sign "<code>=</code>" or vertical bar "<code>|</code>" is inside a wikilink or another template. In the case that there is a bare equal-sign "<code>=</code>" or vertical bar "<code>|</code>", consider using {{tl|Avoid wrap begin}} + {{tl|Avoid wrap end}} instead. However, there are also other workarounds: For text that includes an equal-sign "=", precede the text with <code>1=</code>, use a triple-brace unnamed parameter <code><nowiki>{{{|=}}}</nowiki></code>, or the <code><nowiki>{{=}}</nowiki></code> template. For example: :<code><nowiki>{{Avoid wrap|</nowiki>1=2 + 2 = 4}}</code>, <br /><code><nowiki>{{Avoid wrap|2 + 2 {{{|=}}} 4}}</nowiki></code>, or <br /><code><nowiki>{{Avoid wrap|2 + 2 {{=}} 4}}</nowiki></code> which all render as this: :{{Avoid wrap|1=2 + 2 = 4}}. For text that includes a vertical bar "|", escape the bar(s) with "<code><nowiki>&amp;#124;</nowiki></code>" or "<code><nowiki>{{pipe}}</nowiki></code>" or "<code><nowiki>{{abs}}</nowiki></code>". For instance, put two bars "|6|" like this: :<code><nowiki>{{Avoid wrap|</nowiki>&amp;#124;6&amp;#124; < 7}}</code> &nbsp;or&nbsp; <code><nowiki>{{Avoid wrap|{{pipe}}6{{pipe}} < 7}}</nowiki></code> &nbsp;or&nbsp; <code><nowiki>{{avoid wrap|{{abs|6}} < 7}}</nowiki></code> Which renders this: :{{avoid wrap|&#124;6&#124; < 7}} == Technical details == The actual code that does the job is this [[HTML]] span tag that applies a class to the text inside the template: :{{code|lang=html|1=<span class="avoidwrap" style="display:inline-block;">This text will not wrap</span>}} When there is not enough remaining space on a line to fit the contents of such as block, it will be pushed to the next line in its entirety. However when the block in itself doesn't fit on a single line, line breaking still applies to the 'internal' text block. This is especially advantageous for mobile rendering where the available line width might be much smaller than on desktops and might require line breaks to make longer words or sections fit. == TemplateData == {{TemplateData header}} <templatedata>{ "description": "Prevents word wraps (line breaks) within text or inside a link which contains spaces or hyphens (-).", "params": { "1": { "label": "Text", "description": "Text or link to be protected.", "type": "string", "required": true } } }</templatedata> == See also == {{List of Avoid wrap-like templates}} <includeonly>{{#ifeq:{{SUBPAGENAME}}|sandbox|| <!-- Categories go below this line, please; interwikis go to Wikidata, thank you! --> [[Category:Line-handling templates]] }}</includeonly> 5yxj492uybmul10xk7pw6986oeutcww 797580 797579 2019-09-20T23:03:46Z en>Watchduck 0 shorthand {{awrap}} 797580 wikitext text/x-wiki {{Documentation subpage}} <!-- Categories go where indicated at the bottom of this page, please; interwikis go to Wikidata (see also: [[Wikipedia:Wikidata]]) --> {{High-use| 996942 }} {{COinS safe|n}} <code><nowiki>{{Avoid wrap}}</nowiki></code>, <code><nowiki>{{avoidwrap}}</nowiki></code> or <code><nowiki>{{awrap}}</nowiki></code> avoids wrapping of specific text. To achieve the opposite effect of <code>&#123;&#123;Avoid wrap&#125;&#125;</code>, you can use {{tlx|wbr}}. For more information about wrapping and breaking sentences, see [[Wikipedia:Line-break handling]]. == Usage == :: <code><nowiki>{{Avoid wrap|these words stay together}}</nowiki></code> :: <code><nowiki>{{Avoid wrap|a, b, c, or d.}}</nowiki></code> :: <code><nowiki>{{Avoid wrap| merry-go-round }}</nowiki></code> :: <code><nowiki>{{Avoid wrap| [[4-part harmony]] }}</nowiki></code> :: <code><nowiki>{{Avoid wrap| [[Cascading Style Sheets|CSS]] }}</nowiki></code> :: <code><nowiki>{{Avoid wrap| [[#Examples|Examples section]] }}</nowiki></code> The template names {{tl|Nobr}} and {{tl|Nobreak}} are redirects that may be used instead of "{{tlc|Avoid wrap|…}}". === Examples === {| class=wikitable |"<code>They held <nowiki>{{Avoid wrap|10 kg (22 lb)}}</nowiki> in total.</code>" <br/> May render like this: :They held {{Avoid wrap|10 kg (22 lb)}} <br/>in total. <br/> Or like this: :They held <br/>10 kg (22 lb) in total. <br/> But ''not'' render like this: :They held 10 kg (22 <br/>lb) in total. | "<code>He encountered <nowiki>{{Avoid wrap|a tiger}}</nowiki> in the woods.</code>" <br/> May render like this: :He encountered a tiger <br/>in the woods. <br/> Or like this: :He encountered <br/>a tiger in the woods. <br/> But ''not'' like this: :He encountered a <br/>tiger in the woods. |} === Controlling line-breaking in infoboxes === This template may be used with {{tlx|Wbr}} and {{tlx|Spaces}} to control line-breaking in bulletless lists in infoboxes, to prevent wrapped long entries from being confused with multiple entries. See [[Template:Wbr/doc#Controlling line-breaking in infoboxes]] for details. === Handling equal-sign or bar === [[Help:Template|Templates]] have problems with parameter data that contains [[equal-sign]]s "<code>=</code>" or vertical bars "<code>|</code>" (pipes). Note that this does not apply if the equal-sign "<code>=</code>" or vertical bar "<code>|</code>" is inside a wikilink or another template. In the case that there is a bare equal-sign "<code>=</code>" or vertical bar "<code>|</code>", consider using {{tl|Avoid wrap begin}} + {{tl|Avoid wrap end}} instead. However, there are also other workarounds: For text that includes an equal-sign "=", precede the text with <code>1=</code>, use a triple-brace unnamed parameter <code><nowiki>{{{|=}}}</nowiki></code>, or the <code><nowiki>{{=}}</nowiki></code> template. For example: :<code><nowiki>{{Avoid wrap|</nowiki>1=2 + 2 = 4}}</code>, <br /><code><nowiki>{{Avoid wrap|2 + 2 {{{|=}}} 4}}</nowiki></code>, or <br /><code><nowiki>{{Avoid wrap|2 + 2 {{=}} 4}}</nowiki></code> which all render as this: :{{Avoid wrap|1=2 + 2 = 4}}. For text that includes a vertical bar "|", escape the bar(s) with "<code><nowiki>&amp;#124;</nowiki></code>" or "<code><nowiki>{{pipe}}</nowiki></code>" or "<code><nowiki>{{abs}}</nowiki></code>". For instance, put two bars "|6|" like this: :<code><nowiki>{{Avoid wrap|</nowiki>&amp;#124;6&amp;#124; < 7}}</code> &nbsp;or&nbsp; <code><nowiki>{{Avoid wrap|{{pipe}}6{{pipe}} < 7}}</nowiki></code> &nbsp;or&nbsp; <code><nowiki>{{avoid wrap|{{abs|6}} < 7}}</nowiki></code> Which renders this: :{{avoid wrap|&#124;6&#124; < 7}} == Technical details == The actual code that does the job is this [[HTML]] span tag that applies a class to the text inside the template: :{{code|lang=html|1=<span class="avoidwrap" style="display:inline-block;">This text will not wrap</span>}} When there is not enough remaining space on a line to fit the contents of such as block, it will be pushed to the next line in its entirety. However when the block in itself doesn't fit on a single line, line breaking still applies to the 'internal' text block. This is especially advantageous for mobile rendering where the available line width might be much smaller than on desktops and might require line breaks to make longer words or sections fit. == TemplateData == {{TemplateData header}} <templatedata>{ "description": "Prevents word wraps (line breaks) within text or inside a link which contains spaces or hyphens (-).", "params": { "1": { "label": "Text", "description": "Text or link to be protected.", "type": "string", "required": true } } }</templatedata> == See also == {{List of Avoid wrap-like templates}} <includeonly>{{#ifeq:{{SUBPAGENAME}}|sandbox|| <!-- Categories go below this line, please; interwikis go to Wikidata, thank you! --> [[Category:Line-handling templates]] }}</includeonly> s01nn3vxgqd3cdahvqu0cfyikdql91j 797581 797580 2019-09-25T04:46:19Z en>Willy1018 0 797581 wikitext text/x-wiki {{Documentation subpage}} <!-- Categories go where indicated at the bottom of this page, please; interwikis go to Wikidata (see also: [[Wikipedia:Wikidata]]) --> {{COinS safe|n}} <code><nowiki>{{Avoid wrap}}</nowiki></code>, <code><nowiki>{{avoidwrap}}</nowiki></code> or <code><nowiki>{{awrap}}</nowiki></code> avoids wrapping of specific text. To achieve the opposite effect of <code>&#123;&#123;Avoid wrap&#125;&#125;</code>, you can use {{tlx|wbr}}. For more information about wrapping and breaking sentences, see [[Wikipedia:Line-break handling]]. == Usage == :: <code><nowiki>{{Avoid wrap|these words stay together}}</nowiki></code> :: <code><nowiki>{{Avoid wrap|a, b, c, or d.}}</nowiki></code> :: <code><nowiki>{{Avoid wrap| merry-go-round }}</nowiki></code> :: <code><nowiki>{{Avoid wrap| [[4-part harmony]] }}</nowiki></code> :: <code><nowiki>{{Avoid wrap| [[Cascading Style Sheets|CSS]] }}</nowiki></code> :: <code><nowiki>{{Avoid wrap| [[#Examples|Examples section]] }}</nowiki></code> The template names {{tl|Nobr}} and {{tl|Nobreak}} are redirects that may be used instead of "{{tlc|Avoid wrap|…}}". === Examples === {| class=wikitable |"<code>They held <nowiki>{{Avoid wrap|10 kg (22 lb)}}</nowiki> in total.</code>" <br/> May render like this: :They held {{Avoid wrap|10 kg (22 lb)}} <br/>in total. <br/> Or like this: :They held <br/>10 kg (22 lb) in total. <br/> But ''not'' render like this: :They held 10 kg (22 <br/>lb) in total. | "<code>He encountered <nowiki>{{Avoid wrap|a tiger}}</nowiki> in the woods.</code>" <br/> May render like this: :He encountered a tiger <br/>in the woods. <br/> Or like this: :He encountered <br/>a tiger in the woods. <br/> But ''not'' like this: :He encountered a <br/>tiger in the woods. |} === Controlling line-breaking in infoboxes === This template may be used with {{tlx|Wbr}} and {{tlx|Spaces}} to control line-breaking in bulletless lists in infoboxes, to prevent wrapped long entries from being confused with multiple entries. See [[Template:Wbr/doc#Controlling line-breaking in infoboxes]] for details. === Handling equal-sign or bar === [[Help:Template|Templates]] have problems with parameter data that contains [[equal-sign]]s "<code>=</code>" or vertical bars "<code>|</code>" (pipes). Note that this does not apply if the equal-sign "<code>=</code>" or vertical bar "<code>|</code>" is inside a wikilink or another template. In the case that there is a bare equal-sign "<code>=</code>" or vertical bar "<code>|</code>", consider using {{tl|Avoid wrap begin}} + {{tl|Avoid wrap end}} instead. However, there are also other workarounds: For text that includes an equal-sign "=", precede the text with <code>1=</code>, use a triple-brace unnamed parameter <code><nowiki>{{{|=}}}</nowiki></code>, or the <code><nowiki>{{=}}</nowiki></code> template. For example: :<code><nowiki>{{Avoid wrap|</nowiki>1=2 + 2 = 4}}</code>, <br /><code><nowiki>{{Avoid wrap|2 + 2 {{{|=}}} 4}}</nowiki></code>, or <br /><code><nowiki>{{Avoid wrap|2 + 2 {{=}} 4}}</nowiki></code> which all render as this: :{{Avoid wrap|1=2 + 2 = 4}}. For text that includes a vertical bar "|", escape the bar(s) with "<code><nowiki>&amp;#124;</nowiki></code>" or "<code><nowiki>{{pipe}}</nowiki></code>" or "<code><nowiki>{{abs}}</nowiki></code>". For instance, put two bars "|6|" like this: :<code><nowiki>{{Avoid wrap|</nowiki>&amp;#124;6&amp;#124; < 7}}</code> &nbsp;or&nbsp; <code><nowiki>{{Avoid wrap|{{pipe}}6{{pipe}} < 7}}</nowiki></code> &nbsp;or&nbsp; <code><nowiki>{{avoid wrap|{{abs|6}} < 7}}</nowiki></code> Which renders this: :{{avoid wrap|&#124;6&#124; < 7}} == Technical details == The actual code that does the job is this [[HTML]] span tag that applies a class to the text inside the template: :{{code|lang=html|1=<span class="avoidwrap" style="display:inline-block;">This text will not wrap</span>}} When there is not enough remaining space on a line to fit the contents of such as block, it will be pushed to the next line in its entirety. However when the block in itself doesn't fit on a single line, line breaking still applies to the 'internal' text block. This is especially advantageous for mobile rendering where the available line width might be much smaller than on desktops and might require line breaks to make longer words or sections fit. == TemplateData == {{TemplateData header}} <templatedata>{ "description": "Prevents word wraps (line breaks) within text or inside a link which contains spaces or hyphens (-).", "params": { "1": { "label": "Text", "description": "Text or link to be protected.", "type": "string", "required": true } } }</templatedata> == See also == {{List of Avoid wrap-like templates}} <includeonly>{{#ifeq:{{SUBPAGENAME}}|sandbox|| <!-- Categories go below this line, please; interwikis go to Wikidata, thank you! --> [[Category:Line-handling templates]] }}</includeonly> q1qys4m7xuq7gep9aah3p3fuxis12em 797582 797581 2020-04-23T23:29:43Z en>Vanisaac 0 /* See also */clean up per [[WP:CAT#T]] and [[WP:AWBREQ]] add template:Sandbox other 797582 wikitext text/x-wiki {{Documentation subpage}} <!-- Categories go where indicated at the bottom of this page, please; interwikis go to Wikidata (see also: [[Wikipedia:Wikidata]]) --> {{COinS safe|n}} <code><nowiki>{{Avoid wrap}}</nowiki></code>, <code><nowiki>{{avoidwrap}}</nowiki></code> or <code><nowiki>{{awrap}}</nowiki></code> avoids wrapping of specific text. To achieve the opposite effect of <code>&#123;&#123;Avoid wrap&#125;&#125;</code>, you can use {{tlx|wbr}}. For more information about wrapping and breaking sentences, see [[Wikipedia:Line-break handling]]. == Usage == :: <code><nowiki>{{Avoid wrap|these words stay together}}</nowiki></code> :: <code><nowiki>{{Avoid wrap|a, b, c, or d.}}</nowiki></code> :: <code><nowiki>{{Avoid wrap| merry-go-round }}</nowiki></code> :: <code><nowiki>{{Avoid wrap| [[4-part harmony]] }}</nowiki></code> :: <code><nowiki>{{Avoid wrap| [[Cascading Style Sheets|CSS]] }}</nowiki></code> :: <code><nowiki>{{Avoid wrap| [[#Examples|Examples section]] }}</nowiki></code> The template names {{tl|Nobr}} and {{tl|Nobreak}} are redirects that may be used instead of "{{tlc|Avoid wrap|…}}". === Examples === {| class=wikitable |"<code>They held <nowiki>{{Avoid wrap|10 kg (22 lb)}}</nowiki> in total.</code>" <br/> May render like this: :They held {{Avoid wrap|10 kg (22 lb)}} <br/>in total. <br/> Or like this: :They held <br/>10 kg (22 lb) in total. <br/> But ''not'' render like this: :They held 10 kg (22 <br/>lb) in total. | "<code>He encountered <nowiki>{{Avoid wrap|a tiger}}</nowiki> in the woods.</code>" <br/> May render like this: :He encountered a tiger <br/>in the woods. <br/> Or like this: :He encountered <br/>a tiger in the woods. <br/> But ''not'' like this: :He encountered a <br/>tiger in the woods. |} === Controlling line-breaking in infoboxes === This template may be used with {{tlx|Wbr}} and {{tlx|Spaces}} to control line-breaking in bulletless lists in infoboxes, to prevent wrapped long entries from being confused with multiple entries. See [[Template:Wbr/doc#Controlling line-breaking in infoboxes]] for details. === Handling equal-sign or bar === [[Help:Template|Templates]] have problems with parameter data that contains [[equal-sign]]s "<code>=</code>" or vertical bars "<code>|</code>" (pipes). Note that this does not apply if the equal-sign "<code>=</code>" or vertical bar "<code>|</code>" is inside a wikilink or another template. In the case that there is a bare equal-sign "<code>=</code>" or vertical bar "<code>|</code>", consider using {{tl|Avoid wrap begin}} + {{tl|Avoid wrap end}} instead. However, there are also other workarounds: For text that includes an equal-sign "=", precede the text with <code>1=</code>, use a triple-brace unnamed parameter <code><nowiki>{{{|=}}}</nowiki></code>, or the <code><nowiki>{{=}}</nowiki></code> template. For example: :<code><nowiki>{{Avoid wrap|</nowiki>1=2 + 2 = 4}}</code>, <br /><code><nowiki>{{Avoid wrap|2 + 2 {{{|=}}} 4}}</nowiki></code>, or <br /><code><nowiki>{{Avoid wrap|2 + 2 {{=}} 4}}</nowiki></code> which all render as this: :{{Avoid wrap|1=2 + 2 = 4}}. For text that includes a vertical bar "|", escape the bar(s) with "<code><nowiki>&amp;#124;</nowiki></code>" or "<code><nowiki>{{pipe}}</nowiki></code>" or "<code><nowiki>{{abs}}</nowiki></code>". For instance, put two bars "|6|" like this: :<code><nowiki>{{Avoid wrap|</nowiki>&amp;#124;6&amp;#124; < 7}}</code> &nbsp;or&nbsp; <code><nowiki>{{Avoid wrap|{{pipe}}6{{pipe}} < 7}}</nowiki></code> &nbsp;or&nbsp; <code><nowiki>{{avoid wrap|{{abs|6}} < 7}}</nowiki></code> Which renders this: :{{avoid wrap|&#124;6&#124; < 7}} == Technical details == The actual code that does the job is this [[HTML]] span tag that applies a class to the text inside the template: :{{code|lang=html|1=<span class="avoidwrap" style="display:inline-block;">This text will not wrap</span>}} When there is not enough remaining space on a line to fit the contents of such as block, it will be pushed to the next line in its entirety. However when the block in itself doesn't fit on a single line, line breaking still applies to the 'internal' text block. This is especially advantageous for mobile rendering where the available line width might be much smaller than on desktops and might require line breaks to make longer words or sections fit. == TemplateData == {{TemplateData header}} <templatedata>{ "description": "Prevents word wraps (line breaks) within text or inside a link which contains spaces or hyphens (-).", "params": { "1": { "label": "Text", "description": "Text or link to be protected.", "type": "string", "required": true } } }</templatedata> == See also == {{List of Avoid wrap-like templates}} <includeonly>{{Sandbox other|| <!-- Categories go below this line, please; interwikis go to Wikidata, thank you! --> [[Category:Line-handling templates]] }}</includeonly> ctjn8osxt6tllx36v2pzgencu0fdik0 797583 797582 2020-04-28T17:09:18Z en>Plastikspork 0 /* See also */ Should be added there 797583 wikitext text/x-wiki {{Documentation subpage}} <!-- Categories go where indicated at the bottom of this page, please; interwikis go to Wikidata (see also: [[Wikipedia:Wikidata]]) --> {{COinS safe|n}} <code><nowiki>{{Avoid wrap}}</nowiki></code>, <code><nowiki>{{avoidwrap}}</nowiki></code> or <code><nowiki>{{awrap}}</nowiki></code> avoids wrapping of specific text. To achieve the opposite effect of <code>&#123;&#123;Avoid wrap&#125;&#125;</code>, you can use {{tlx|wbr}}. For more information about wrapping and breaking sentences, see [[Wikipedia:Line-break handling]]. == Usage == :: <code><nowiki>{{Avoid wrap|these words stay together}}</nowiki></code> :: <code><nowiki>{{Avoid wrap|a, b, c, or d.}}</nowiki></code> :: <code><nowiki>{{Avoid wrap| merry-go-round }}</nowiki></code> :: <code><nowiki>{{Avoid wrap| [[4-part harmony]] }}</nowiki></code> :: <code><nowiki>{{Avoid wrap| [[Cascading Style Sheets|CSS]] }}</nowiki></code> :: <code><nowiki>{{Avoid wrap| [[#Examples|Examples section]] }}</nowiki></code> The template names {{tl|Nobr}} and {{tl|Nobreak}} are redirects that may be used instead of "{{tlc|Avoid wrap|…}}". === Examples === {| class=wikitable |"<code>They held <nowiki>{{Avoid wrap|10 kg (22 lb)}}</nowiki> in total.</code>" <br/> May render like this: :They held {{Avoid wrap|10 kg (22 lb)}} <br/>in total. <br/> Or like this: :They held <br/>10 kg (22 lb) in total. <br/> But ''not'' render like this: :They held 10 kg (22 <br/>lb) in total. | "<code>He encountered <nowiki>{{Avoid wrap|a tiger}}</nowiki> in the woods.</code>" <br/> May render like this: :He encountered a tiger <br/>in the woods. <br/> Or like this: :He encountered <br/>a tiger in the woods. <br/> But ''not'' like this: :He encountered a <br/>tiger in the woods. |} === Controlling line-breaking in infoboxes === This template may be used with {{tlx|Wbr}} and {{tlx|Spaces}} to control line-breaking in bulletless lists in infoboxes, to prevent wrapped long entries from being confused with multiple entries. See [[Template:Wbr/doc#Controlling line-breaking in infoboxes]] for details. === Handling equal-sign or bar === [[Help:Template|Templates]] have problems with parameter data that contains [[equal-sign]]s "<code>=</code>" or vertical bars "<code>|</code>" (pipes). Note that this does not apply if the equal-sign "<code>=</code>" or vertical bar "<code>|</code>" is inside a wikilink or another template. In the case that there is a bare equal-sign "<code>=</code>" or vertical bar "<code>|</code>", consider using {{tl|Avoid wrap begin}} + {{tl|Avoid wrap end}} instead. However, there are also other workarounds: For text that includes an equal-sign "=", precede the text with <code>1=</code>, use a triple-brace unnamed parameter <code><nowiki>{{{|=}}}</nowiki></code>, or the <code><nowiki>{{=}}</nowiki></code> template. For example: :<code><nowiki>{{Avoid wrap|</nowiki>1=2 + 2 = 4}}</code>, <br /><code><nowiki>{{Avoid wrap|2 + 2 {{{|=}}} 4}}</nowiki></code>, or <br /><code><nowiki>{{Avoid wrap|2 + 2 {{=}} 4}}</nowiki></code> which all render as this: :{{Avoid wrap|1=2 + 2 = 4}}. For text that includes a vertical bar "|", escape the bar(s) with "<code><nowiki>&amp;#124;</nowiki></code>" or "<code><nowiki>{{pipe}}</nowiki></code>" or "<code><nowiki>{{abs}}</nowiki></code>". For instance, put two bars "|6|" like this: :<code><nowiki>{{Avoid wrap|</nowiki>&amp;#124;6&amp;#124; < 7}}</code> &nbsp;or&nbsp; <code><nowiki>{{Avoid wrap|{{pipe}}6{{pipe}} < 7}}</nowiki></code> &nbsp;or&nbsp; <code><nowiki>{{avoid wrap|{{abs|6}} < 7}}</nowiki></code> Which renders this: :{{avoid wrap|&#124;6&#124; < 7}} == Technical details == The actual code that does the job is this [[HTML]] span tag that applies a class to the text inside the template: :{{code|lang=html|1=<span class="avoidwrap" style="display:inline-block;">This text will not wrap</span>}} When there is not enough remaining space on a line to fit the contents of such as block, it will be pushed to the next line in its entirety. However when the block in itself doesn't fit on a single line, line breaking still applies to the 'internal' text block. This is especially advantageous for mobile rendering where the available line width might be much smaller than on desktops and might require line breaks to make longer words or sections fit. == TemplateData == {{TemplateData header}} <templatedata>{ "description": "Prevents word wraps (line breaks) within text or inside a link which contains spaces or hyphens (-).", "params": { "1": { "label": "Text", "description": "Text or link to be protected.", "type": "string", "required": true } } }</templatedata> == See also == {{List of nowrap-like templates}} <includeonly>{{Sandbox other|| <!-- Categories go below this line, please; interwikis go to Wikidata, thank you! --> [[Category:Line-handling templates]] }}</includeonly> gx6ow6d0yolwq8lww4sb76xua9u4097 797584 797583 2025-04-12T05:44:40Z en>Joeyconnick 0 /* Handling equal-sign or bar */ list actual templates 797584 wikitext text/x-wiki {{Documentation subpage}} <!-- Categories go where indicated at the bottom of this page, please; interwikis go to Wikidata (see also: [[Wikipedia:Wikidata]]) --> {{COinS safe|n}} <code><nowiki>{{Avoid wrap}}</nowiki></code>, <code><nowiki>{{avoidwrap}}</nowiki></code> or <code><nowiki>{{awrap}}</nowiki></code> avoids wrapping of specific text. To achieve the opposite effect of <code>&#123;&#123;Avoid wrap&#125;&#125;</code>, you can use {{tlx|wbr}}. For more information about wrapping and breaking sentences, see [[Wikipedia:Line-break handling]]. == Usage == :: <code><nowiki>{{Avoid wrap|these words stay together}}</nowiki></code> :: <code><nowiki>{{Avoid wrap|a, b, c, or d.}}</nowiki></code> :: <code><nowiki>{{Avoid wrap| merry-go-round }}</nowiki></code> :: <code><nowiki>{{Avoid wrap| [[4-part harmony]] }}</nowiki></code> :: <code><nowiki>{{Avoid wrap| [[Cascading Style Sheets|CSS]] }}</nowiki></code> :: <code><nowiki>{{Avoid wrap| [[#Examples|Examples section]] }}</nowiki></code> The template names {{tl|Nobr}} and {{tl|Nobreak}} are redirects that may be used instead of "{{tlc|Avoid wrap|…}}". === Examples === {| class=wikitable |"<code>They held <nowiki>{{Avoid wrap|10 kg (22 lb)}}</nowiki> in total.</code>" <br/> May render like this: :They held {{Avoid wrap|10 kg (22 lb)}} <br/>in total. <br/> Or like this: :They held <br/>10 kg (22 lb) in total. <br/> But ''not'' render like this: :They held 10 kg (22 <br/>lb) in total. | "<code>He encountered <nowiki>{{Avoid wrap|a tiger}}</nowiki> in the woods.</code>" <br/> May render like this: :He encountered a tiger <br/>in the woods. <br/> Or like this: :He encountered <br/>a tiger in the woods. <br/> But ''not'' like this: :He encountered a <br/>tiger in the woods. |} === Controlling line-breaking in infoboxes === This template may be used with {{tlx|Wbr}} and {{tlx|Spaces}} to control line-breaking in bulletless lists in infoboxes, to prevent wrapped long entries from being confused with multiple entries. See [[Template:Wbr/doc#Controlling line-breaking in infoboxes]] for details. === Handling equal-sign or bar === [[Help:Template|Templates]] have problems with parameter data that contains [[equal-sign]]s "<code>=</code>" or vertical bars "<code>|</code>" (pipes). Note that this does not apply if the equal-sign "<code>=</code>" or vertical bar "<code>|</code>" is inside a wikilink or another template. In the case that there is a bare equal-sign "<code>=</code>" or vertical bar "<code>|</code>", consider using {{tl|Nowrap begin}} + {{tl|Nowrap end}} instead. However, there are also other workarounds: For text that includes an equal-sign "=", precede the text with <code>1=</code>, use a triple-brace unnamed parameter <code><nowiki>{{{|=}}}</nowiki></code>, or the <code><nowiki>{{=}}</nowiki></code> template. For example: :<code><nowiki>{{Avoid wrap|</nowiki>1=2 + 2 = 4}}</code>, <br /><code><nowiki>{{Avoid wrap|2 + 2 {{{|=}}} 4}}</nowiki></code>, or <br /><code><nowiki>{{Avoid wrap|2 + 2 {{=}} 4}}</nowiki></code> which all render as this: :{{Avoid wrap|1=2 + 2 = 4}}. For text that includes a vertical bar "|", escape the bar(s) with "<code><nowiki>&amp;#124;</nowiki></code>" or "<code><nowiki>{{pipe}}</nowiki></code>" or "<code><nowiki>{{abs}}</nowiki></code>". For instance, put two bars "|6|" like this: :<code><nowiki>{{Avoid wrap|</nowiki>&amp;#124;6&amp;#124; < 7}}</code> &nbsp;or&nbsp; <code><nowiki>{{Avoid wrap|{{pipe}}6{{pipe}} < 7}}</nowiki></code> &nbsp;or&nbsp; <code><nowiki>{{avoid wrap|{{abs|6}} < 7}}</nowiki></code> Which renders this: :{{avoid wrap|&#124;6&#124; < 7}} == Technical details == The actual code that does the job is this [[HTML]] span tag that applies a class to the text inside the template: :{{code|lang=html|1=<span class="avoidwrap" style="display:inline-block;">This text will not wrap</span>}} When there is not enough remaining space on a line to fit the contents of such as block, it will be pushed to the next line in its entirety. However when the block in itself doesn't fit on a single line, line breaking still applies to the 'internal' text block. This is especially advantageous for mobile rendering where the available line width might be much smaller than on desktops and might require line breaks to make longer words or sections fit. == TemplateData == {{TemplateData header}} <templatedata>{ "description": "Prevents word wraps (line breaks) within text or inside a link which contains spaces or hyphens (-).", "params": { "1": { "label": "Text", "description": "Text or link to be protected.", "type": "string", "required": true } } }</templatedata> == See also == {{List of nowrap-like templates}} <includeonly>{{Sandbox other|| <!-- Categories go below this line, please; interwikis go to Wikidata, thank you! --> [[Category:Line-handling templates]] }}</includeonly> cez8bzef10nkdxt8uizzfogdp00cehc 797585 797584 2026-06-09T01:28:27Z SM7 3953 8 revisions imported from [[:en:Template:Avoid_wrap/doc]] 797584 wikitext text/x-wiki {{Documentation subpage}} <!-- Categories go where indicated at the bottom of this page, please; interwikis go to Wikidata (see also: [[Wikipedia:Wikidata]]) --> {{COinS safe|n}} <code><nowiki>{{Avoid wrap}}</nowiki></code>, <code><nowiki>{{avoidwrap}}</nowiki></code> or <code><nowiki>{{awrap}}</nowiki></code> avoids wrapping of specific text. To achieve the opposite effect of <code>&#123;&#123;Avoid wrap&#125;&#125;</code>, you can use {{tlx|wbr}}. For more information about wrapping and breaking sentences, see [[Wikipedia:Line-break handling]]. == Usage == :: <code><nowiki>{{Avoid wrap|these words stay together}}</nowiki></code> :: <code><nowiki>{{Avoid wrap|a, b, c, or d.}}</nowiki></code> :: <code><nowiki>{{Avoid wrap| merry-go-round }}</nowiki></code> :: <code><nowiki>{{Avoid wrap| [[4-part harmony]] }}</nowiki></code> :: <code><nowiki>{{Avoid wrap| [[Cascading Style Sheets|CSS]] }}</nowiki></code> :: <code><nowiki>{{Avoid wrap| [[#Examples|Examples section]] }}</nowiki></code> The template names {{tl|Nobr}} and {{tl|Nobreak}} are redirects that may be used instead of "{{tlc|Avoid wrap|…}}". === Examples === {| class=wikitable |"<code>They held <nowiki>{{Avoid wrap|10 kg (22 lb)}}</nowiki> in total.</code>" <br/> May render like this: :They held {{Avoid wrap|10 kg (22 lb)}} <br/>in total. <br/> Or like this: :They held <br/>10 kg (22 lb) in total. <br/> But ''not'' render like this: :They held 10 kg (22 <br/>lb) in total. | "<code>He encountered <nowiki>{{Avoid wrap|a tiger}}</nowiki> in the woods.</code>" <br/> May render like this: :He encountered a tiger <br/>in the woods. <br/> Or like this: :He encountered <br/>a tiger in the woods. <br/> But ''not'' like this: :He encountered a <br/>tiger in the woods. |} === Controlling line-breaking in infoboxes === This template may be used with {{tlx|Wbr}} and {{tlx|Spaces}} to control line-breaking in bulletless lists in infoboxes, to prevent wrapped long entries from being confused with multiple entries. See [[Template:Wbr/doc#Controlling line-breaking in infoboxes]] for details. === Handling equal-sign or bar === [[Help:Template|Templates]] have problems with parameter data that contains [[equal-sign]]s "<code>=</code>" or vertical bars "<code>|</code>" (pipes). Note that this does not apply if the equal-sign "<code>=</code>" or vertical bar "<code>|</code>" is inside a wikilink or another template. In the case that there is a bare equal-sign "<code>=</code>" or vertical bar "<code>|</code>", consider using {{tl|Nowrap begin}} + {{tl|Nowrap end}} instead. However, there are also other workarounds: For text that includes an equal-sign "=", precede the text with <code>1=</code>, use a triple-brace unnamed parameter <code><nowiki>{{{|=}}}</nowiki></code>, or the <code><nowiki>{{=}}</nowiki></code> template. For example: :<code><nowiki>{{Avoid wrap|</nowiki>1=2 + 2 = 4}}</code>, <br /><code><nowiki>{{Avoid wrap|2 + 2 {{{|=}}} 4}}</nowiki></code>, or <br /><code><nowiki>{{Avoid wrap|2 + 2 {{=}} 4}}</nowiki></code> which all render as this: :{{Avoid wrap|1=2 + 2 = 4}}. For text that includes a vertical bar "|", escape the bar(s) with "<code><nowiki>&amp;#124;</nowiki></code>" or "<code><nowiki>{{pipe}}</nowiki></code>" or "<code><nowiki>{{abs}}</nowiki></code>". For instance, put two bars "|6|" like this: :<code><nowiki>{{Avoid wrap|</nowiki>&amp;#124;6&amp;#124; < 7}}</code> &nbsp;or&nbsp; <code><nowiki>{{Avoid wrap|{{pipe}}6{{pipe}} < 7}}</nowiki></code> &nbsp;or&nbsp; <code><nowiki>{{avoid wrap|{{abs|6}} < 7}}</nowiki></code> Which renders this: :{{avoid wrap|&#124;6&#124; < 7}} == Technical details == The actual code that does the job is this [[HTML]] span tag that applies a class to the text inside the template: :{{code|lang=html|1=<span class="avoidwrap" style="display:inline-block;">This text will not wrap</span>}} When there is not enough remaining space on a line to fit the contents of such as block, it will be pushed to the next line in its entirety. However when the block in itself doesn't fit on a single line, line breaking still applies to the 'internal' text block. This is especially advantageous for mobile rendering where the available line width might be much smaller than on desktops and might require line breaks to make longer words or sections fit. == TemplateData == {{TemplateData header}} <templatedata>{ "description": "Prevents word wraps (line breaks) within text or inside a link which contains spaces or hyphens (-).", "params": { "1": { "label": "Text", "description": "Text or link to be protected.", "type": "string", "required": true } } }</templatedata> == See also == {{List of nowrap-like templates}} <includeonly>{{Sandbox other|| <!-- Categories go below this line, please; interwikis go to Wikidata, thank you! --> [[Category:Line-handling templates]] }}</includeonly> cez8bzef10nkdxt8uizzfogdp00cehc विकिपीडिया:Moving a page 4 101024 797594 2026-06-09T03:45:17Z SM7 3953 पन्ना [[विकिपीडिया:पन्ना के स्थानांतरण कइल]] पर अनुप्रेषित कइल गइल 797594 wikitext text/x-wiki #REDIRECT [[विकिपीडिया:पन्ना के स्थानांतरण कइल]] bkidz1s7n06ba8oqubg5l16fl3tb728 विकिपीडिया:Requested moves/Closing instructions 4 101025 797595 2026-06-09T03:51:22Z SM7 3953 पन्ना बनावल गइल "{{Explanatory supplement|interprets=Wikipedia:Requested moves|shortcut=WP:RMCI|shortcut2=WP:RMCLOSE}} {{Nutshell|#Don't close requested moves if you are involved; #If you are not an administrator you should be cautious when closing contentious requests; #Determine [[WP:CONSENSUS|consensus]] on the request or relist it for further discussion; #Investigate the page history of the target page title; if ''minor'', it may be deleted; i..." के साथ 797595 wikitext text/x-wiki {{Explanatory supplement|interprets=Wikipedia:Requested moves|shortcut=WP:RMCI|shortcut2=WP:RMCLOSE}} {{Nutshell|#Don't close requested moves if you are involved; #If you are not an administrator you should be cautious when closing contentious requests; #Determine [[WP:CONSENSUS|consensus]] on the request or relist it for further discussion; #Investigate the page history of the target page title; if ''minor'', it may be deleted; if ''major'', perform a [[Wikipedia:How to fix cut-and-paste moves|history merge]], [[WP:SWAP|history swap]], or archive and place a link on the talk page; #Close the move request on the talk page using {{tls|RM top}} and {{tls|RM bottom}}; #If renamed then [[WP:POSTMOVE|clean up after the move]] }} {{Simple|Wikipedia:Simple RM closing instructions}} The following are suggestions for closing [[Wikipedia:Requested moves]] discussions. These should generally be applied only after the normal seven-day listing period has elapsed. These suggestions are addressed to formal move requests that occur on talk pages, i.e. ''controversial'' move requests, but are instructive as to the necessary page history investigation and preservation and cleanup procedures advisable upon any move. Requests listed in the [[Wikipedia:Requested moves/Technical requests|technical requests]] section can be simply removed after they have been processed. Where technical moves are contested, move the listing to the [[Wikipedia:Requested moves/Technical requests#Contested technical requests|contested technical requests section]]. Failure of an RM closer to follow the spirit and intent of these instructions, especially about [[#Determining consensus|properly weighing consensus with applicable policies and guidelines]], may result in the initiation of a '''[[WP:Move review|Move Review]]'''. ==Who can close requested moves== ===Conflicts of interest=== {{shortcut|WP:RMCOI}} An [[WP:INVOLVED|involved editor]], whether an administrator [[WP:NACINV|or otherwise]], may '''not''' close a move request (with certain exceptions, detailed below). These criteria here are significantly stricter; this is intentional, befitting the less urgent nature of requested moves. You are considered involved if you have ever: *opened a request to move the page; *supported or opposed a request to move the page *closed a previous controversial request to move the page *[[WP:BOLD|boldly]] moved the page to a title you prefer (i.e., not including reverting [[WP:MOVEVANDAL|page move vandalism]] or performing a [[WP:RM/TR|technical request]]) *commented on any talk page in a way that has indicated a clear position on the specific move request *edited the page in a way that demonstrates a clear position on the move request An involved editor may comment in a move discussion, solicit a closure, or make a new move request at a later date, but may not close an open move request. If you are involved, trust the process and leave the closure to one of the many, many other editors on Wikipedia capable of closing move discussions. After at least a week of discussion has taken place, you can solicit a closure by [[Wikipedia:Closure requests|requesting that an impartial editor assess consensus]]. The closer should be familiar with all relevant policies and guidelines, especially [[Wikipedia:Article titles|those relating to article titles]] and [[Wikipedia:Disambiguation|disambiguation]] and the procedures described on this page. Do not ask for a specific closer under any circumstances. ===Non-admin closure=== {{shortcut|WP:RMNAC}} Experienced and uninvolved registered editors in good standing are encouraged to close requested move discussions. Any non-admin closure (NAC) must be explicitly declared with an appropriate template (such as {{tls|RMnac}}) placed directly after the reasoning for the close within the {{tls|RM top}} template (or use the {{para|nac}} parameter in the closing template). Non-administrators are reminded that closing a discussion calls for an impartial assessment of consensus or lack thereof, although arguments supported by directly relevant policy and guidelines are given more weight (while keeping broader Wikipedia policy, guidelines, and consensus in mind). Any editor wishing to express an opinion on the requested move should join the discussion, not close it. Anyone, administrator or not, who wishes to close a requested move must be very familiar with the policies and guidelines associated with it (especially [[Wikipedia:Article titles]], [[Wikipedia:Disambiguation]], and [[Wikipedia:Consensus]]), and ideally will have participated in several move requests previously. All closures of requested moves are subject to being taken to review at [[WP:Move review]] ([[WP:MRV]]), but assuming the criteria above are met, the mere fact that the closer was not an admin is not sufficient reason to reverse a closure. Indeed, many high-profile, controversial move requests have been closed as NACs, taken to [[WP:MRV]], and affirmed there. While non-admins should be cautious (as indeed all move closers should be) when closing discussions where significant contentious debate among participants is unresolved, any experienced and uninvolved editor in good standing may close any RM debate. Occasionally, a move involves a redirect with multiple revisions, and requires technical intervention. Editors are permitted to close the discussion and file a [[WP:RMTM|technical move]] with a link to the closed discussion. The results of discussions in favor of moves should generally be respected by the administrator (or page mover). If an administrator notices a clearly improper move closure, they should revert the closure and re-open the discussion. In any case where a non-admin closer does resort to a technical move request, the closer should actively monitor that request, and be ready and willing to perform all tidying after the move (as [[#Cleaning up after the move|instructed below]]), such as updating fair-use rationales and navbox links included on the page. If a non-admin closer is not willing to wait for the technical move and to perform the follow up in this manner, they should only close requested moves that do not require technical intervention. ====Closure by a page mover==== {{see|Wikipedia:Page mover|Template:RMnac}} {{shortcut|WP:RMPMC}} Editors with the page mover permission can perform certain technical moves without administrator assistance, such as a move over a redirect with more than one edit via a [[WP:PMVR#rr|round-robin]] page swap. A user is granted the page mover right after demonstrating a good understanding of the Wikipedia page naming system and decent page moving experience. Since closure by a page mover is a type of non-admin closure, it should be labeled as such using {{tls|RMnac}}, {{tls|RMpmc}} or equivalent (or use the {{para|pmc}} parameter in the closing template). ===Early closure=== {{shortcut|WP:RMEC}} In general, move discussions should remain open for at least seven days (168 hours) to allow interested editors adequate time to participate. However, when no one has commented yet, or if opposition is unanimous, discussions may be closed prior to the seven-day timeframe for the following reasons: * As long as no one has suggested any outcome besides ''not moving'', the ''proposer'' of a move may withdraw their request. The closure should say that the request was '''withdrawn'''. * Furthermore, any editor may end such a move request with a '''procedural close''': ** if the request was initiated via [[Wikipedia:Blocking policy#Evasion and enforcement|block evasion]]. Under this circumstance, the closer may have been involved in the discussion; or ** to centralize related discussions that should have been a multi-page discussion. * When the outcome of the move discussion is or has become almost certain and there is clearly no need to prolong discussion further, it may be closed early under the '''[[Wikipedia:Snowball clause|snowball clause]]'''. This clause should not be used to close a discussion when a particular outcome is merely "likely" or "highly likely", and there is a genuine and reasoned basis for disagreement. Move discussions are not a vote; it is important to be reasonably sure that there is little or no chance of accidentally excluding significant input or perspectives, or changing the weight of different views, if closed early. Especially, closers should beware of interpreting "early pile on" as necessarily showing how a discussion will end up. This can sometimes happen when a topic attracts high levels of attention from those engaged (or having a specific view) but slower attention from other less involved editors, perhaps with other points of view. It can sometimes be better to allow a few extra days even if the current discussion seems very clearly to hold one opinion, to avoid precluding significant input and as a courtesy to ensure that it really will be a snowball. ==Determining consensus== {{shortcut|WP:RMCIDC}} {{See also|Wikipedia:Advice on closing discussions}} [[Wikipedia:Consensus|Consensus]] is determined not just by considering the preferences of the participants in a given discussion, but also by evaluating their arguments, assigning due weight accordingly, and giving due consideration to the relevant consensus of the Wikipedia community in general as reflected in applicable policy, guidelines and naming conventions. {{shortcut|WP:RMNOMIN}} '''{{vanchor|No minimum participation is required}}''' for requested moves. If no one has objected, go ahead and perform the move as requested, unless it is out of keeping with [[Wikipedia:Naming conventions|naming conventions]] or is otherwise in conflict with applicable guidelines or policy. Further, any move request that is out of keeping with [[Wikipedia:Naming conventions|naming conventions]] or is otherwise in conflict with applicable guideline and policy, unless there is a very good reason to [[WP:IAR|ignore rules]], should be closed without moving regardless of how many of the participants support it. Remember, the participants in any given discussion represent only a tiny fraction of the Wikipedia community whose consensus is reflected in the policy, guidelines and conventions to which all titles are to adhere. Thus, closers are expected to be familiar with such matters, so that they have the ability to make these assessments. If objections have been raised, then the discussion should be evaluated just like any other discussion on Wikipedia: lack of consensus among participants along with no clear indication from policy and conventions normally means that no change happens (though like [[Articles for Deletion|WP:AFD]], this is not a vote and the quality of an argument is more important than whether it comes from a minority or a majority). However, sometimes a requested move is filed in response to a recent move from a long existing name that cannot be undone without administrative help. Therefore, if no consensus has been reached, the closer should move the article back to the most recent stable title. If no recent title has been stable, then the article should be moved to the title used by the first major contributor after the article ceased to be a stub. {{shortcut|WP:RMNCREV}}{{anchor|RM no consensus revert}} Note that according to {{section link|Wikipedia:Consensus|No consensus}}: {{cquote|When article title discussions end without consensus, the [[Wikipedia:TITLECHANGES|applicable policy]] preserves the most recent stable title. If there is no prior stable title, then the default is the title used by the first major contributor after the article ceased to be a [[Wikipedia:Stub|stub]].}} Therefore, if a page has been moved from a long-standing title, and it is not possible to move the page back to its original title during the discussion, the default title will be the title prior to the contested move. For example, if an article is created at [[Soda can]] and stays there for years prior to being [[WP:BOLD]]ly moved to [[Pop can]], and a move request is filed leading to a decision of "no consensus", the article must be moved back to its longstanding title. This is the case even if the ''original'' page was placed at [[Pop can]] or [[Fizzy drink can]] or [[Orange-flavored soft drink can]], as long as [[Soda can]] took over through consensus and can be determined to be the actual long-standing title. ===Relisting=== {{For|the instructions on relisting move discussions|Wikipedia:Requested moves#Relisting a requested move}} Relisting is an option when a discussion cannot otherwise be closed, usually due to lack of consensus. Editors are under no obligation to wait to close a move request after it is relisted. Once a move request has been open for the full seven days, it may be closed at any time by an uninvolved editor. Closers wait mainly for general agreement, for consensus, to emerge. '''Exception:''' if a page is added while relisting. Sometimes editors may not realize that to move page A to B, page B must first be moved to C. When a new page move request is added to one that is already seven days old, the RMCD bot places the notification tag on the newly added page that day, and the discussion must go a full seven days (more) before being closed. It is helpful to closers when relisters leave a note just below the nomination that this happened. ====Relisting and participating==== A relister may later become a participant or closer in the requested move discussion or survey. ===Only move involved pages=== A move request about moving X to Y should never be closed in such a way as to require page Z to move if Z wasn't listed in the original move request (see [[#Moves of other pages|below]]). <!--This rule has been in place, in one way or another, since 2014. Please do not remove without discussion.--> Where a discussion would result in the original title pointing to a "Foo (disambiguation)" page, the practice of pagemovers has been to immediately move the disambiguation page to the base page name, per [[WP:MALPLACED]]. This is because a disambiguation page is presumed to belong at the base page name unless that title is taken by a primary topic. ==<span class="anchor" id="Three possible outcomes"></span>Write a clear, concise closing statement== <!-- This Anchor tag serves to provide a permanent target for incoming section links. Please do not remove it, nor modify it, except to add another appropriate anchor. If you modify the section title, please anchor the old title. It is always best to anchor an old section header that has been changed so that links to it will not be broken. See [[Template:Anchor]] for details. This template is {{subst:Anchor comment}} --> {{shortcut|WP:THREEOUTCOMES}} {{further|#Determining consensus}} There are generally three different outcomes for consensus in requested moves. The closer should clearly show which outcome has taken place so that other editors may quickly see the progression of consensus regarding the title; it is much easier to move an article that has never had a firm consensus about the title. # '''Not moved''' (or '''consensus not to move''') should be used when a consensus ''has'' formed to keep the current title and ''not'' rename the article(s) in question. For instance, a proposal to rename [[Bob Dylan]] to [[Squeezy Joe]] would likely result in everyone (or nearly everyone) agreeing that the proposed move should not take place; this notifies other editors that they should probably not propose this move in the future until and unless circumstances change. There is a positive consensus found, and that consensus is for the page to stay exactly where it is. # '''No consensus''' should be used when there is neither a consensus to move ''nor'' a consensus to keep the current title. This may be because a discussion has fractured into several possible titles and none seem especially suitable, or simply because equally strong arguments and appeals to Wikipedia policy and outside sources were found on both sides, without any clear reason to move the page found in the discussion. Of course, as elsewhere on Wikipedia, this usually means that no action is to be taken at the present time. # '''Moved''' (or '''consensus to move''') should be used when consensus ''is'' found to rename the page. If there is any question whatsoever as to ''which'' title it should be moved to, please note this in the closing summary (e.g. "'''moved to [[Squeezy José]]'''"). This almost always sets a consensus for the new title, and further requests to move the page are likely to fail unless new information or arguments are brought forth. While it is usually bad form to re-request a move if consensus ''is'' found against it (until and unless circumstances change), it is ''not'' considered bad form to re-raise a request that found "no consensus" to move. (Successful move re-requests generally, though not always, take place at least three months after the previous one. An exception is when the no-consensus move discussion suggests a clear, new course of action.) ===Discussions involving multiple options=== {{seealso|WP:Bartender}} {{shortcut|WP:NOGOODOPTIONS|WP:OTHEROPTIONS}} Most move requests are simple. Alice proposes that we move X to Y. Bob, Carol and Dave chime in. Edgar analyzes the discussion and decides whether there is a consensus, and then gives one of the three outcomes above. But sometimes things get complicated. Alice proposes moving X to Y, but then Bob raises real concerns about Y, and proposes Z instead. Carol says, no, we should stay with Y. Dave says we actually need to keep the article at X. Edgar shows up and is very confused. What does he do? If you as a closer are in doubt because too many titles have been proposed and there's no real consensus anywhere, it is generally best to '''close as no consensus''' and allow someone to re-propose the move to a more specific or better title. {{anchor|NOTCURRENTTITLE}}{{shortcut|WP:NOTCURRENTTITLE|WP:NOTCURRENT}} But then again, there are situations where multiple names have been proposed and no consensus arises out of any, except that it '''is''' determined that the current title should ''not'' host the article. (There are good arguments for Y, and there are good arguments for Z, but there are virtually ''no'' good arguments for it to stay at X.) In these difficult circumstances, the closer should pick the best title of the options available, and then make clear that while consensus has rejected the former title (and no request to bring it back should be made lightly), there is no consensus for the title actually chosen. Because such closures are inherently complex and require the closer to use more independent judgment than normal, it is ''strongly encouraged'' to provide an explicit closing statement in such closures. As this result does not indicate a consensus for the chosen title, anyone who objects to the closer's decision may make another move request ''at any time'', and is advised to create such a request instead of taking the closure to [[WP:MRV|move review]]. If a result of the NOGOODOPTIONS close would be the disambiguation of a title that was not included in the original proposal, and for which there has been no notice on its corresponding Talk page, then instead of closing, a notice should be placed on that article's talk page, and the proposal relisted. No title should be disambiguated as a result of an RM without notice on its Talk page. ==Closing the requested move== When you complete an entry on the project (whether the move was accepted or rejected), '''remove the {{tl|requested move/dated}} tag from the talk page''', or change {{tla|requested move/dated|requested move/'''dated'''}} to <nowiki>{{</nowiki>[[WP:Subst|'''subst''']]:[[Template:Requested move/end|requested move/'''end''']]}}. You should also add and sign a comment to indicate whether the move was accepted or rejected in the discussion area for the requested move. To ease this process, there are several scripts that can help; the [[Wikipedia:User scripts/List#Closing discussions|most popular]] version that is actively maintained being [[User:BilledMammal/Move+]]. Alternatives include [[User:DannyS712/PageMoverClosure]] and [[User:Andy M. Wang/closeRM]]. While historically, there have been other options for closing the move request survey on the affected article's talk page, nowadays we exclusively use the twin templates {{tlxs|RM top|'''result of the discussion'''.}} and {{tlxs|RM bottom}}. ===Step-by-step closing procedure=== After clicking the [edit] tab next to the move discussion, you may follow these step-by-step instructions for closing an RM discussion: {|class="wikitable" style="font-size: 90%; text-align: center; width: 100%;" |- !Before closing !After closing !Description |- | style="background-color: lightgray"|<nowiki>==Requested move==</nowiki> | style="background-color: lightgray"|<nowiki>==Requested move==</nowiki> |Leave the heading alone; the close starts below it. |- | style="background-color: yellow"|<nowiki>{{Requested move/dated|Foo}}</nowiki> | style="background-color: lightgreen"|<nowiki>{{subst:RM top|'''RESULT.'''</nowiki><span class="anonymous-show extendedconfirmed-show">{{!}}<span class="extendedmover-show">pm</span>nac=yes</span><nowiki>}}</nowiki> |Replace text on left with text on right. |- |style="background-color: lightgray"|''DISCUSSION'' |style="background-color: lightgray"|''DISCUSSION'' |Body of the discussion stays unchanged. |- | | style="background-color: lightgreen;"|<nowiki>{{subst:RM bottom}}</nowiki> |Add the bottom template. |- |} *If additional explanation is provided as to why you have closed the move discussion as a certain result, add additional comments immediately after '''<nowiki>'''RESULT'''.</nowiki>'''. *Save the page with an edit summary such as "<code>Closing requested move survey; page moved/not moved</code>". After closing, the page should look similar to this: <div class="boilerplate mw-archivedtalk" style="background-color: var(--background-color-success-subtle, #efe); color: var(--color-base, #000); margin: 0; padding: 0 10px 0 10px; border: 1px dotted var(--border-color-subtle, #AAAAAA);"><!-- Template:RM top --> :''The following is a closed discussion of a [[Wikipedia:Requested moves|requested move]]. <span style="color: var(--color-error, red);">'''Please do not modify it.'''</span> Subsequent comments should be made in a new section on the talk page. Editors desiring to contest the closing decision should consider a [[Wikipedia:move review|move review]] after discussing it on the closer's talk page. No further edits should be made to this discussion.'' The result of the move request was: '''RESULT'''. [Additional comments]. <span class="anonymous-show extendedconfirmed-show"><small>([[Wikipedia:Requested moves/Closing instructions#Non-admin closure|non-admin closure]])</small> </span>[[User:Example|Example]] ([[User talk:Example|talk]]) {{CURRENTTIME}}, {{CURRENTDAY}} {{CURRENTMONTHNAME}} {{CURRENTYEAR}} (UTC) ---- [[Foo]] → {{no redirect|1=Foobar}} – rationale of nominator. [[User:Example|Example]] ([[User talk:Example|talk]]) {{CURRENTTIME}}, {{CURRENTDAY}} {{CURRENTMONTHNAME}} {{CURRENTYEAR}} (UTC) *'''Supports/Opposes''' with discussion {{RM bottom}} {{hidden end}} </div> ===Add <span class="nowrap">&#123;&#123;</span>[[Template:Old moves|Old moves]]<span class="nowrap">&#125;&#125;</span> to the talk page if so desired=== After the move is complete, the {{tl|Old moves}} template may be added to the top of the talk page (or updated if already present), allowing editors to see previous move discussions that might otherwise be archived. This is helpful for titles that are likely to be challenged again, so that any would-be re-proposer can make reference to previous arguments and consider how a consensus may be formed to move. In the case of pages with multiple move discussions, these templates should always be added/updated after the closure of an RM. ===Automatic removals=== Once the article's talk page has been updated, there's no need to return to the [[Wikipedia:Requested moves]] page and delete the article's entry there; this will be performed automatically by a bot. Likewise, {{u|RMCD bot}} will remove the {{tl|Title notice}} from the page itself within 15 minutes. ===Using move protection during RMs and immediately after RM closes=== {{see also|WP:MOVP}} Some RM discussions are contentious; undiscussed, unilateral page moves during a discussion or page moves made immediately after and contrary to an RM close decision are disruptive and hurt the integrity of the RM process. Admins monitoring RM discussions should use their discretion to ''move protect'' articles during contentious RM discussions when they believe a premature, undiscussed unilateral move would be disruptive to the discussion. The same discretion should be used to start or continue move protection immediately after the RM close. Generally, such move protection should be limited to no more than 30 days under normal circumstances. The RM closing comment should reference the move protection. == Moving procedures == ===Edit history of destination page=== {{main|Wikipedia:Moving a page#Swapping two pages|Wikipedia:Page mover#Round-robin page moves}} The majority of target names for move requests already exist as [[WP:REDIRECT|redirects]] to the present names. Whether a redirect or otherwise, that existing target title should be investigated to see whether it has a minor or major [[Help:Page history|page history]]. If it has a ''minor'' page history, generally meaning it only existed as a redirect, and was never a duplicate article, never had content that was cut and pasted to the present title, nor merged there, ''it may simply be deleted.'' However, if the target page title has a ''major'' history it should '''never''' be simply deleted, as [[WP:CWW|we need to retain such page histories for proper copyright attribution]]. There are three ways to deal with target pages with major histories, dependent on circumstances: #For page histories resulting from cut and paste moves, the ''correct'' way to fix this is to merge the page history of the present article and the redirect, using the procedure outlined at [[Wikipedia:History merging]]. On rare occasions, this procedure will not work correctly. Once a history merge is done, it cannot easily be undone, so don't pick this option unless it is definitely the right one. You can request history merges at [[Wikipedia:Requests for history merge]]. # For duplicate articles and merged content, or alternatively for cut and paste moves, the page histories of the article and the redirect can be swapped with a round-robin move. For cut and paste moves this leaves a bifurcated history, but has less chance of causing problems. Simply move the redirect with a major history to a temporary name (<code>Draft:Move/''NAME''</code> is suggested), suppressing the creation of a redirect (in the event you forget to suppress the redirect, delete the same); next, move the article to the move target location, again suppressing the redirect and deleting the same if you forget; next, move the redirect from its temporary location to the title at which the article you just moved was formerly located. At this point the redirect will be pointing at itself; re-target it to point to the new location of the article. # Another option is for redirect pages with major histories to be archived into a talk namespace, and a link then placed on the article's [[Wikipedia:talk page|talk page]]. (An example of such a page is at [[Talk:Network SouthEast]], which was originally created as a duplicate article at [[Network SouthEast]] and later archived, when the original article was moved from [[Network South East]]). ===Cleaning up after the move=== You should not close any move discussion if you are unwilling to do the necessary clean up tasks.{{#lsth:Wikipedia:Moving a page|<span class="anchor" id="POST"></span><span class="anchor" id="Post-move cleanup"></span>Cleaning up after a move}} ===<span class="anchor" id="NOTOTHERPAGES"></span>Moves of other pages=== {{shortcut|WP:NOTOTHERPAGES}} If a page is to be moved as the result of a move request, mention should be made of this in the move proposal and a notice should be placed on the talk page of the article to be moved (unless of course it is hosting the discussion). Generally, a move request on whether to move [[X]] to [[Y]] should have no impact on page [[Z]]'s title, unless it is initiated as a {{tl|multi-move request}} that mentions moving Z as a possibility. This is because the editors most interested and aware of Z are not able to contribute their expertise to the naming discussion, since it's happening at a different place without any notice given. These situations often come up when [[Foo (barge)]] is proposed to move to, say, [[Foo (enormous sailing thing)]], and someone mentions that they think the barge is actually the primary topic. A consensus of these barge enthusiasts may then informally suggest that the existing article [[Foo]] be moved to [[Foo (bar)]], without actually notifying Foobar-interested editors by signaling at [[Talk:Foo]] that a move request involving that page is taking place. This often leads to strife and another, more contentious move request. If consensus at X signals that Z should be disambiguated, relist the move proposal and leave notice at [[Talk:Z]]. Even if local consensus is clear, or when following [[WP:NOGOODOPTIONS]], when closing a move request '''do not disambiguate article titles for which there has been no notice on the corresponding Talk page'''. Disambiguating is always potentially controversial. Again, in these cases relist the move request and post a notice on the to-be-disambiguated article's Talk page. Such notice is ''not'' required when an article is being moved to the base name of its current disambiguated title. [[Category:Requested moves| ]] [[Category:Administrator instructions]] en5wmq9535c09j9mmj0nk40rvhmdkko हैगियोग्राफी 0 101026 797597 2026-06-09T06:52:55Z SM7 3953 पन्ना बनावल गइल "'''हैगियोग्राफी''' ({{Langx|en|hagiography}})<ref>{{cite OED |hagiography}}</ref> कवनो संत, धार्मिक नेता भा आध्यात्मिक महापुरुष के जीवनी-लेखन के कहल जाला। व्यापक अर्थ में ई दुनिया के कवनो भी धर्म में उपदेशक,..." के साथ 797597 wikitext text/x-wiki '''हैगियोग्राफी''' ({{Langx|en|hagiography}})<ref>{{cite OED |hagiography}}</ref> कवनो संत, धार्मिक नेता भा आध्यात्मिक महापुरुष के जीवनी-लेखन के कहल जाला। व्यापक अर्थ में ई दुनिया के कवनो भी धर्म में उपदेशक, पुजारी, धर्म संस्थापक, संत, साधु, साध्वी भा पूजनीय धार्मिक व्यक्तित्व के प्रशंसात्मक आ आदर्शीकृत जीवन-वृत्तांत हो सकेला।<ref name="MongeChirico2016">{{cite book|author=Rico G. Monge|editor=Rico G. Monge, Kerry P. C. San Chirico and Rachel J. Smith|title=Hagiography and Religious Truth: Case Studies in the Abrahamic and Dharmic Traditions|url=https://books.google.com/books?id=nDnCDAAAQBAJ| year=2016|publisher=Bloomsbury Publishing|isbn=978-1474235792|pages=7–22}}</ref><ref>{{cite book|author=Jeanette Blonigen Clancy|title=Beyond Parochial Faith: A Catholic Confesses|url=https://books.google.com/books?id=efCaDwAAQBAJ&pg=PA137 |year=2019|publisher=Wipf and Stock Publishers|isbn=978-1532672828|page=137}}</ref><ref>{{cite book | last=Rapp | first=Claudia | title=Byzantine Religious Culture | chapter=Hagiography and the Cult of Saints in the Light of Epigraphy and Acclamations | publisher=Brill Academic| year=2012 | pages=289–311 | isbn=978-9004226494 | doi=10.1163/9789004226494_017 }}</ref> प्रारंभिक [[ईसाई धर्म|ईसाई परंपरा]] में हैगियोग्राफी के रूप कई प्रकार के मिले लें। इनहन में संत लोगन के जिनगी-कथा — ''वीटा'' (''vita'') , जेकर अर्थ "जीवन" होला आ जवन अधिकांश मध्यकालीन जीवनी के शीर्षक के शुरुआत में मिलेला — संत द्वारा कइल गइल कार्य आ चमत्कारन के वर्णन, उनका शहादत भा बलिदान के विवरण (जेकरा के ''पैसियो'' कहल जाला), भा एह सभ के मिश्रित रूप शामिल रहे ला। ईसाई धर्म के अलावे अइसन लेखन [[बौद्ध धर्म]],<ref>Jonathan Augustine (2012), ''Buddhist Hagiography in Early Japan'', Routledge, {{ISBN|978-0415646291}}</ref> [[हिंदू धर्म]]<ref>David Lorenzen (2006), ''Who Invented Hinduism?'', [[Yoda Press]], {{ISBN|978-8190227261}}, pp.&nbsp;120–121</ref> ताओ धर्म, सिख धर्म आ इस्लाम में भी मिले ला। एह प्रकार के रचना सभ के मुख्य उद्देश्य खाली ऐतिहासिक जानकारी देहल ना, बल्कि संत भा धार्मिक व्यक्तित्व के आदर्श जीवन, आध्यात्मिक महत्त्व आ धार्मिक प्रभाव के उजागर कइल भी होला। [[इतिहास]] के आधुनिक बिद्वान लोग अइसन जीवनी-लेखन के कुछ हीन नजरिया से देके ला काहें से कि इनहन में लेखक अपना बिसाय के प्रति कुछ बेसिए तारीफी भा आदर के झुकाव लिहले रहे ला, तटस्थ ना होला। {{clear}} == संदर्भ == {{Reflist|29em}} jp32pn5b157bv4kaa6gbo48t00csqjp 797598 797597 2026-06-09T06:57:31Z SM7 3953 [[विकिपीडिया:हॉट-कैट|हॉट-कैट]] द्वारा +[[श्रेणी:इतिहास लेखन]]; +[[श्रेणी:जीवनी]]; +[[श्रेणी:मध्यकालीन इतिहास]] 797598 wikitext text/x-wiki '''हैगियोग्राफी''' ({{Langx|en|hagiography}})<ref>{{cite OED |hagiography}}</ref> कवनो संत, धार्मिक नेता भा आध्यात्मिक महापुरुष के जीवनी-लेखन के कहल जाला। व्यापक अर्थ में ई दुनिया के कवनो भी धर्म में उपदेशक, पुजारी, धर्म संस्थापक, संत, साधु, साध्वी भा पूजनीय धार्मिक व्यक्तित्व के प्रशंसात्मक आ आदर्शीकृत जीवन-वृत्तांत हो सकेला।<ref name="MongeChirico2016">{{cite book|author=Rico G. Monge|editor=Rico G. Monge, Kerry P. C. San Chirico and Rachel J. Smith|title=Hagiography and Religious Truth: Case Studies in the Abrahamic and Dharmic Traditions|url=https://books.google.com/books?id=nDnCDAAAQBAJ| year=2016|publisher=Bloomsbury Publishing|isbn=978-1474235792|pages=7–22}}</ref><ref>{{cite book|author=Jeanette Blonigen Clancy|title=Beyond Parochial Faith: A Catholic Confesses|url=https://books.google.com/books?id=efCaDwAAQBAJ&pg=PA137 |year=2019|publisher=Wipf and Stock Publishers|isbn=978-1532672828|page=137}}</ref><ref>{{cite book | last=Rapp | first=Claudia | title=Byzantine Religious Culture | chapter=Hagiography and the Cult of Saints in the Light of Epigraphy and Acclamations | publisher=Brill Academic| year=2012 | pages=289–311 | isbn=978-9004226494 | doi=10.1163/9789004226494_017 }}</ref> प्रारंभिक [[ईसाई धर्म|ईसाई परंपरा]] में हैगियोग्राफी के रूप कई प्रकार के मिले लें। इनहन में संत लोगन के जिनगी-कथा — ''वीटा'' (''vita'') , जेकर अर्थ "जीवन" होला आ जवन अधिकांश मध्यकालीन जीवनी के शीर्षक के शुरुआत में मिलेला — संत द्वारा कइल गइल कार्य आ चमत्कारन के वर्णन, उनका शहादत भा बलिदान के विवरण (जेकरा के ''पैसियो'' कहल जाला), भा एह सभ के मिश्रित रूप शामिल रहे ला। ईसाई धर्म के अलावे अइसन लेखन [[बौद्ध धर्म]],<ref>Jonathan Augustine (2012), ''Buddhist Hagiography in Early Japan'', Routledge, {{ISBN|978-0415646291}}</ref> [[हिंदू धर्म]]<ref>David Lorenzen (2006), ''Who Invented Hinduism?'', [[Yoda Press]], {{ISBN|978-8190227261}}, pp.&nbsp;120–121</ref> ताओ धर्म, सिख धर्म आ इस्लाम में भी मिले ला। एह प्रकार के रचना सभ के मुख्य उद्देश्य खाली ऐतिहासिक जानकारी देहल ना, बल्कि संत भा धार्मिक व्यक्तित्व के आदर्श जीवन, आध्यात्मिक महत्त्व आ धार्मिक प्रभाव के उजागर कइल भी होला। [[इतिहास]] के आधुनिक बिद्वान लोग अइसन जीवनी-लेखन के कुछ हीन नजरिया से देके ला काहें से कि इनहन में लेखक अपना बिसाय के प्रति कुछ बेसिए तारीफी भा आदर के झुकाव लिहले रहे ला, तटस्थ ना होला। {{clear}} == संदर्भ == {{Reflist|29em}} [[श्रेणी:इतिहास लेखन]] [[श्रेणी:जीवनी]] [[श्रेणी:मध्यकालीन इतिहास]] g9yjsxl8hchif6guw3fcmkic42u75go 797599 797598 2026-06-09T06:58:45Z SM7 3953 [[User:SM7/stubsorter|Stubsorter]] के मदद से {{Hist-stub}} जोड़ल गइल। 797599 wikitext text/x-wiki '''हैगियोग्राफी''' ({{Langx|en|hagiography}})<ref>{{cite OED |hagiography}}</ref> कवनो संत, धार्मिक नेता भा आध्यात्मिक महापुरुष के जीवनी-लेखन के कहल जाला। व्यापक अर्थ में ई दुनिया के कवनो भी धर्म में उपदेशक, पुजारी, धर्म संस्थापक, संत, साधु, साध्वी भा पूजनीय धार्मिक व्यक्तित्व के प्रशंसात्मक आ आदर्शीकृत जीवन-वृत्तांत हो सकेला।<ref name="MongeChirico2016">{{cite book|author=Rico G. Monge|editor=Rico G. Monge, Kerry P. C. San Chirico and Rachel J. Smith|title=Hagiography and Religious Truth: Case Studies in the Abrahamic and Dharmic Traditions|url=https://books.google.com/books?id=nDnCDAAAQBAJ| year=2016|publisher=Bloomsbury Publishing|isbn=978-1474235792|pages=7–22}}</ref><ref>{{cite book|author=Jeanette Blonigen Clancy|title=Beyond Parochial Faith: A Catholic Confesses|url=https://books.google.com/books?id=efCaDwAAQBAJ&pg=PA137 |year=2019|publisher=Wipf and Stock Publishers|isbn=978-1532672828|page=137}}</ref><ref>{{cite book | last=Rapp | first=Claudia | title=Byzantine Religious Culture | chapter=Hagiography and the Cult of Saints in the Light of Epigraphy and Acclamations | publisher=Brill Academic| year=2012 | pages=289–311 | isbn=978-9004226494 | doi=10.1163/9789004226494_017 }}</ref> प्रारंभिक [[ईसाई धर्म|ईसाई परंपरा]] में हैगियोग्राफी के रूप कई प्रकार के मिले लें। इनहन में संत लोगन के जिनगी-कथा — ''वीटा'' (''vita'') , जेकर अर्थ "जीवन" होला आ जवन अधिकांश मध्यकालीन जीवनी के शीर्षक के शुरुआत में मिलेला — संत द्वारा कइल गइल कार्य आ चमत्कारन के वर्णन, उनका शहादत भा बलिदान के विवरण (जेकरा के ''पैसियो'' कहल जाला), भा एह सभ के मिश्रित रूप शामिल रहे ला। ईसाई धर्म के अलावे अइसन लेखन [[बौद्ध धर्म]],<ref>Jonathan Augustine (2012), ''Buddhist Hagiography in Early Japan'', Routledge, {{ISBN|978-0415646291}}</ref> [[हिंदू धर्म]]<ref>David Lorenzen (2006), ''Who Invented Hinduism?'', [[Yoda Press]], {{ISBN|978-8190227261}}, pp.&nbsp;120–121</ref> ताओ धर्म, सिख धर्म आ इस्लाम में भी मिले ला। एह प्रकार के रचना सभ के मुख्य उद्देश्य खाली ऐतिहासिक जानकारी देहल ना, बल्कि संत भा धार्मिक व्यक्तित्व के आदर्श जीवन, आध्यात्मिक महत्त्व आ धार्मिक प्रभाव के उजागर कइल भी होला। [[इतिहास]] के आधुनिक बिद्वान लोग अइसन जीवनी-लेखन के कुछ हीन नजरिया से देके ला काहें से कि इनहन में लेखक अपना बिसाय के प्रति कुछ बेसिए तारीफी भा आदर के झुकाव लिहले रहे ला, तटस्थ ना होला। {{clear}} == संदर्भ == {{Reflist|29em}} [[श्रेणी:इतिहास लेखन]] [[श्रेणी:जीवनी]] [[श्रेणी:मध्यकालीन इतिहास]] {{Hist-stub}} q97dzgy0y6no14ldq515ce9kilww2eg अस्सी घाट 0 101027 797604 2026-06-09T09:03:20Z SM7 3953 नया आधार लेख 797604 wikitext text/x-wiki '''अस्सी घाट''' भारत के [[बनारस]] शहर में स्थित [[बनारस के घाट|घाटन]] में सबसे दक्खिनी माथ पर स्थित घाट हवे। बनारस आवे वाला बहुत लोग खातिर ई घाट खास पहिचान रखे ला, काहे कि एह इलाका में लंबा समय तक रहे वाला विदेशी विद्यार्थी, रिसर्चर आ पर्यटक लोग के संख्या बेसी रहेला। अस्सी घाट पर हर बिहान "सुबहे-बनारस" नाम के सांस्कृतिक आ आध्यात्मिक कार्यक्रम आयोजित कइल जाला। एह कार्यक्रम में योग, ध्यान, वैदिक मंत्रोच्चार, भजन-कीर्तन आ सांस्कृतिक प्रस्तुति सभ शामिल रहेली, जे बनारस के धार्मिक आ सांस्कृतिक परंपरा के जीवंत रूप प्रस्तुत करे लीं। ar1dje3dclx4nvbe3jv696hjr2oy00m 797605 797604 2026-06-09T09:03:38Z SM7 3953 [[विकिपीडिया:हॉट-कैट|हॉट-कैट]] द्वारा [[श्रेणी:बनारस के घाट]] जोड़ल गइल 797605 wikitext text/x-wiki '''अस्सी घाट''' भारत के [[बनारस]] शहर में स्थित [[बनारस के घाट|घाटन]] में सबसे दक्खिनी माथ पर स्थित घाट हवे। बनारस आवे वाला बहुत लोग खातिर ई घाट खास पहिचान रखे ला, काहे कि एह इलाका में लंबा समय तक रहे वाला विदेशी विद्यार्थी, रिसर्चर आ पर्यटक लोग के संख्या बेसी रहेला। अस्सी घाट पर हर बिहान "सुबहे-बनारस" नाम के सांस्कृतिक आ आध्यात्मिक कार्यक्रम आयोजित कइल जाला। एह कार्यक्रम में योग, ध्यान, वैदिक मंत्रोच्चार, भजन-कीर्तन आ सांस्कृतिक प्रस्तुति सभ शामिल रहेली, जे बनारस के धार्मिक आ सांस्कृतिक परंपरा के जीवंत रूप प्रस्तुत करे लीं। [[श्रेणी:बनारस के घाट]] argxbsory3l9vm87ony12o44rnbjsn6 797606 797605 2026-06-09T09:04:26Z SM7 3953 [[विकिपीडिया:हॉट-कैट|हॉट-कैट]] द्वारा [[श्रेणी:बनारस]] जोड़ल गइल 797606 wikitext text/x-wiki '''अस्सी घाट''' भारत के [[बनारस]] शहर में स्थित [[बनारस के घाट|घाटन]] में सबसे दक्खिनी माथ पर स्थित घाट हवे। बनारस आवे वाला बहुत लोग खातिर ई घाट खास पहिचान रखे ला, काहे कि एह इलाका में लंबा समय तक रहे वाला विदेशी विद्यार्थी, रिसर्चर आ पर्यटक लोग के संख्या बेसी रहेला। अस्सी घाट पर हर बिहान "सुबहे-बनारस" नाम के सांस्कृतिक आ आध्यात्मिक कार्यक्रम आयोजित कइल जाला। एह कार्यक्रम में योग, ध्यान, वैदिक मंत्रोच्चार, भजन-कीर्तन आ सांस्कृतिक प्रस्तुति सभ शामिल रहेली, जे बनारस के धार्मिक आ सांस्कृतिक परंपरा के जीवंत रूप प्रस्तुत करे लीं। [[श्रेणी:बनारस के घाट]] [[श्रेणी:बनारस]] 920osmt6pu0zlf33yk50xl20ml7fpso 797607 797606 2026-06-09T09:05:04Z SM7 3953 + navigation template 797607 wikitext text/x-wiki '''अस्सी घाट''' भारत के [[बनारस]] शहर में स्थित [[बनारस के घाट|घाटन]] में सबसे दक्खिनी माथ पर स्थित घाट हवे। बनारस आवे वाला बहुत लोग खातिर ई घाट खास पहिचान रखे ला, काहे कि एह इलाका में लंबा समय तक रहे वाला विदेशी विद्यार्थी, रिसर्चर आ पर्यटक लोग के संख्या बेसी रहेला। अस्सी घाट पर हर बिहान "सुबहे-बनारस" नाम के सांस्कृतिक आ आध्यात्मिक कार्यक्रम आयोजित कइल जाला। एह कार्यक्रम में योग, ध्यान, वैदिक मंत्रोच्चार, भजन-कीर्तन आ सांस्कृतिक प्रस्तुति सभ शामिल रहेली, जे बनारस के धार्मिक आ सांस्कृतिक परंपरा के जीवंत रूप प्रस्तुत करे लीं। {{बनारस}} [[श्रेणी:बनारस के घाट]] [[श्रेणी:बनारस]] rtd43vsr27h7b1qaag4m69zrn6djymo