LCOV - code coverage report
Current view: top level - lib/third_party/linkify - uri.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 1 73 1.4 %
Date: 2024-10-08 18:18:56 Functions: 0 0 -

          Line data    Source code
       1             : // Originally from linkify: https://github.com/Cretezy/linkify/blob/dfb3e43b0e56452bad584ddb0bf9b73d8db0589f/lib/src/url.dart
       2             : //
       3             : // Removed handling of `removeWWW` and `humanize`.
       4             : // Removed auto-appending of `http(s)://` to the readable url
       5             : //
       6             : // MIT License
       7             : //
       8             : // Copyright (c) 2019 Charles-William Crete
       9             : //
      10             : // Permission is hereby granted, free of charge, to any person obtaining a copy
      11             : // of this software and associated documentation files (the "Software"), to deal
      12             : // in the Software without restriction, including without limitation the rights
      13             : // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
      14             : // copies of the Software, and to permit persons to whom the Software is
      15             : // furnished to do so, subject to the following conditions:
      16             : //
      17             : // The above copyright notice and this permission notice shall be included in all
      18             : // copies or substantial portions of the Software.
      19             : //
      20             : // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      21             : // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      22             : // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      23             : // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      24             : // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      25             : // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
      26             : // SOFTWARE.
      27             : 
      28             : import 'package:cwtch/config.dart';
      29             : 
      30             : import 'linkify.dart';
      31             : 
      32           0 : final _urlRegex = RegExp(
      33             :   r'^(.*?)((?:https?:\/\/|www\.)[^\s/$.?#].[^\s]*)',
      34             :   caseSensitive: false,
      35             :   dotAll: true,
      36             : );
      37             : 
      38           0 : final _looseUrlRegex = RegExp(
      39             :   r'^(.*?)((https?:\/\/)?(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,16}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*))',
      40             :   caseSensitive: false,
      41             :   dotAll: true,
      42             : );
      43             : 
      44             : class Formatter {
      45             :   final RegExp expression;
      46             :   final LinkifyElement Function(String) element;
      47             : 
      48           0 :   Formatter(this.expression, this.element);
      49             : }
      50             : 
      51             : // regex to match **bold**
      52           0 : final _boldRegex = RegExp(
      53             :   r'^(.*?)(\*\*([^*]+)\*\*)',
      54             :   caseSensitive: false,
      55             :   dotAll: true,
      56             : );
      57             : 
      58             : // regex to match *italic*
      59           0 : final _italicRegex = RegExp(
      60             :   r'^(.*?)(\*([^*]+)\*)',
      61             :   caseSensitive: false,
      62             :   dotAll: true,
      63             : );
      64             : 
      65             : // regex to match ^superscript^
      66           0 : final _superRegex = RegExp(
      67             :   r'^(.*?)(\^([^\^]*)\^)',
      68             :   caseSensitive: false,
      69             :   dotAll: true,
      70             : );
      71             : 
      72             : // regex to match ^subscript^
      73           0 : final _subRegex = RegExp(
      74             :   r'^(.*?)(\_([^\_]*)\_)',
      75             :   caseSensitive: false,
      76             :   dotAll: true,
      77             : );
      78             : 
      79             : // regex to match ~~strikethrough~~
      80           0 : final _strikeRegex = RegExp(
      81             :   r'^(.*?)(\~\~([^\~]*)\~\~)',
      82             :   caseSensitive: false,
      83             :   dotAll: true,
      84             : );
      85             : 
      86             : // regex to match `code`
      87           0 : final _codeRegex = RegExp(
      88             :   r'^(.*?)(\`([^\`]*)\`)',
      89             :   caseSensitive: false,
      90             :   dotAll: true,
      91             : );
      92             : 
      93             : class UrlLinkifier extends Linkifier {
      94           4 :   const UrlLinkifier();
      95             : 
      96           0 :   List<LinkifyElement> replaceAndParse(tle, TextElement element, RegExpMatch match, List<LinkifyElement> list, options) {
      97           0 :     final text = element.text.replaceFirst(match.group(0)!, '');
      98             : 
      99           0 :     if (match.group(1)?.isNotEmpty == true) {
     100           0 :       list.addAll(parse([TextElement(match.group(1)!)], options));
     101             :     }
     102             : 
     103           0 :     if (match.group(2)?.isNotEmpty == true) {
     104           0 :       list.add(tle(match.group(2)!));
     105             :     }
     106             : 
     107           0 :     if (text.isNotEmpty) {
     108           0 :       list.addAll(parse([TextElement(text)], options));
     109             :     }
     110             :     return list;
     111             :   }
     112             : 
     113           0 :   List<LinkifyElement> parseFormatting(element, options) {
     114           0 :     var list = <LinkifyElement>[];
     115             : 
     116             :     // code -> bold -> italic -> super -> sub -> strike
     117             :     // note we don't currently allow combinations of these elements the first
     118             :     // one to match a given set will be the only style applied - this will be fixed
     119             : 
     120             :     // Per #836, URLs should be lower priority than code. Since URL parsing logic
     121             :     // is more complicated than a simple regex and is implemented in the recursing
     122             :     // function, code regex is injected before the URL logic one level up.
     123             :     // To preserve behaviour when the clickable links experiment is disabled,
     124             :     // the code regex is preserved here for the time being.
     125             :     // Eventually this might all be replaced by a more advanced formatting parser.
     126           0 :     final formattingPrecedence = [
     127           0 :       Formatter(_codeRegex, CodeElement.new),
     128           0 :       Formatter(_boldRegex, BoldElement.new),
     129           0 :       Formatter(_italicRegex, ItalicElement.new),
     130           0 :       Formatter(_superRegex, SuperElement.new),
     131             :       // Formatter(_subRegex, SubElement.new),
     132           0 :       Formatter(_strikeRegex, StrikeElement.new)
     133             :     ];
     134             : 
     135             :     // Loop through the formatters in with precedence and break when something is found...
     136           0 :     for (var formatter in formattingPrecedence) {
     137           0 :       var formattingMatch = formatter.expression.firstMatch(element.text);
     138             :       if (formattingMatch != null) {
     139           0 :         list = replaceAndParse(formatter.element, element, formattingMatch, list, options);
     140             :         break;
     141             :       }
     142             :     }
     143             : 
     144             :     // catch all case where we didn't match anything and so need to return back
     145             :     // the unformatted text
     146             :     // conceptually this is Formatter((.*), TextElement.new)
     147           0 :     if (list.isEmpty) {
     148           0 :       list.add(element);
     149             :     }
     150             : 
     151             :     return list;
     152             :   }
     153             : 
     154           0 :   @override
     155             :   List<LinkifyElement> parse(elements, options) {
     156           0 :     var list = <LinkifyElement>[];
     157             : 
     158           0 :     elements.forEach((element) {
     159           0 :       if (element is TextElement) {
     160           0 :         if (options.parseLinks == false && options.messageFormatting == false) {
     161           0 :           list.add(element);
     162           0 :         } else if (options.parseLinks == true) {
     163             :           // Per #836, code block formatting needs to take precedence over URLs.
     164             :           // Only in this combination of conditionals is this additional logic required.
     165           0 :           var codeBlockMatch = _codeRegex.firstMatch(element.text);
     166           0 :           if (codeBlockMatch != null && options.messageFormatting == true) {
     167           0 :             final text = element.text.replaceFirst(codeBlockMatch.group(0)!, '');
     168           0 :             if (codeBlockMatch.group(1)?.isNotEmpty == true) {
     169           0 :               list.addAll(parse([TextElement(codeBlockMatch.group(1)!)], options));
     170             :             }
     171           0 :             list.add(CodeElement(codeBlockMatch.group(2)!));
     172           0 :             if (text.isNotEmpty) {
     173           0 :               list.addAll(parse([TextElement(text)], options));
     174             :             }
     175             :           } else {
     176             :             // check if there is a link...
     177           0 :             var match = options.looseUrl ? _looseUrlRegex.firstMatch(element.text) : _urlRegex.firstMatch(element.text);
     178             : 
     179             :             // if not then we only have to consider formatting...
     180             :             if (match == null) {
     181             :               // only do formatting if message formatting is enabled
     182           0 :               if (options.messageFormatting == false) {
     183           0 :                 list.add(element);
     184             :               } else {
     185             :                 // add all the formatting elements contained in this text
     186           0 :                 list.addAll(parseFormatting(element, options));
     187             :               }
     188             :             } else {
     189           0 :               final text = element.text.replaceFirst(match.group(0)!, '');
     190             : 
     191           0 :               if (match.group(1)?.isNotEmpty == true) {
     192             :                 // we match links first and the feed everything before the link
     193             :                 // back through the parser
     194           0 :                 list.addAll(parse([TextElement(match.group(1)!)], options));
     195             :               }
     196             : 
     197           0 :               if (match.group(2)?.isNotEmpty == true) {
     198           0 :                 var originalUrl = match.group(2)!;
     199             :                 String? end;
     200             : 
     201           0 :                 if ((options.excludeLastPeriod) && originalUrl[originalUrl.length - 1] == ".") {
     202             :                   end = ".";
     203           0 :                   originalUrl = originalUrl.substring(0, originalUrl.length - 1);
     204             :                 }
     205             : 
     206             :                 var url = originalUrl;
     207             : 
     208             :                 // If protocol has not been specified then append a protocol
     209             :                 // to the start of the URL so that it can be opened...
     210           0 :                 if (!url.startsWith("https://") && !url.startsWith("http://")) {
     211           0 :                   url = "https://" + url;
     212             :                 }
     213             : 
     214           0 :                 list.add(UrlElement(url, originalUrl));
     215             : 
     216             :                 if (end != null) {
     217           0 :                   list.add(TextElement(end));
     218             :                 }
     219             :               }
     220             : 
     221           0 :               if (text.isNotEmpty) {
     222           0 :                 list.addAll(parse([TextElement(text)], options));
     223             :               }
     224             :             }
     225             :           }
     226           0 :         } else if (options.messageFormatting == true) {
     227             :           // we can jump straight to message formatting...
     228           0 :           list.addAll(parseFormatting(element, options));
     229             :         } else {
     230             :           // unreachable - if we get here then there is something wrong in the above logic since every combination of
     231             :           // formatting options should have already been accounted for.
     232           0 :           EnvironmentConfig.debugLog("'unreachable' code path in formatting has been triggered. this is very likely a bug - please report $options");
     233             :         }
     234             :       }
     235             :     });
     236             : 
     237             :     return list;
     238             :   }
     239             : }
     240             : 
     241             : /// Represents an element containing a link
     242             : class UrlElement extends LinkableElement {
     243           0 :   UrlElement(String url, [String? text]) : super(text, url);
     244             : 
     245           0 :   @override
     246             :   String toString() {
     247           0 :     return "LinkElement: '$url' ($text)";
     248             :   }
     249             : 
     250           0 :   @override
     251           0 :   bool operator ==(other) => equals(other);
     252             : 
     253           0 :   @override
     254           0 :   bool equals(other) => other is UrlElement && super.equals(other);
     255             : }

Generated by: LCOV version 1.14