«ميدياويكي:Common.js»: الفرق بين المراجعتين

من Sudan Memory
اذهب إلى: تصفح، ابحث
سطر ٤: سطر ٤:
 
// Formate a poem
 
// Formate a poem
 
// 2024-02-27: Created by Abdalla G. M. Ahmed
 
// 2024-02-27: Created by Abdalla G. M. Ahmed
 +
// 2024-03-07: Updated to include annotation
 
// =============================================================================
 
// =============================================================================
  
سطر ٥١: سطر ٥٢:
 
     while (result.length > 0 && !result[result.length - 1]) { result.pop(); }  // Remove empty elements from the end
 
     while (result.length > 0 && !result[result.length - 1]) { result.pop(); }  // Remove empty elements from the end
 
     return result;
 
     return result;
 +
}
 +
 +
// -----------------------------------------------------------------------------
 +
// Escape special characters in strings used to build RegExp
 +
// -----------------------------------------------------------------------------
 +
 +
function escRegExp(string) {
 +
    return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');                      // $& means the whole matched string
 +
}
 +
 +
// -----------------------------------------------------------------------------
 +
// Replace digits to use Hindi Arabic (٠-٩)
 +
// -----------------------------------------------------------------------------
 +
 +
function arabic2hindi(input) {
 +
    return input.replace(/\d/g, function(match) {
 +
        return String.fromCharCode(parseInt(match, 10) + 0x0660);
 +
    });
 
}
 
}
  
سطر ٥٨: سطر ٧٧:
  
 
function poemFormat(poem) {
 
function poemFormat(poem) {
     var bWidth = parseFloat(poem.getAttribute('data-bWidth') || 0);             // Overall with of beit
+
     const bGap = 50;                                                            // This is twice the sum of widths of bPrefix and bSuffix in CSS
     var bGap = parseFloat(poem.getAttribute('data-bGap') || 50);
+
    const bStaggerOffset = 75;                                                  // Should match CSS
 +
    const bWidthMax = 450;                                                      // This should match the CSS declaration of the width of poemBody div less padding and bPrefix/bSuffix width
 +
    const elementSeparator = '<->';                                             // A placeholder for separating identified parts during processing
 +
    const partSeparator = '<=>';                                                // A placeholder for separating prefix/body/suffix of a part
 +
    const titlePlcHldr = '<+>';                                                // A placeholder for in-place footnotes, distinct from '+' characters that may exist in the text
 +
    const ftntPlcHldr = '<*>';                                                  // A placeholder for gutter footnotes
 +
     var lines = poem.innerHTML;                                                // We get the HTML to retain inline tags <a,i,b,s,u>
 +
    var bWidth = parseFloat(poem.getAttribute('data-bWidth') || 0);             // Overall width of beit
 
     var pWidth = [0, 0, 0, 0, 0];                                              // Widths of part for different number of parts, index 0 is used for centered verses
 
     var pWidth = [0, 0, 0, 0, 0];                                              // Widths of part for different number of parts, index 0 is used for centered verses
 
     var nGaps  = [2, 0, 1, 2, 3];                                              // Number of gaps for different number of parts, initially assuming no leading gaps
 
     var nGaps  = [2, 0, 1, 2, 3];                                              // Number of gaps for different number of parts, initially assuming no leading gaps
     var fontStyle = getFontStyle(poem);
+
     var fontStyle = getFontStyle(poem);                                         // For determining the rendered widths of parts
     var lines = (
+
     var staggered = false;                                                      // Whether the poem is fed as one half per line, indenting second halves
         poem
+
    // -------------------------------------------------------------------------
         .innerHTML                                                              // We get the HTML to retain some tags like <a,i,b>
+
    // Extract referenced footnotes
 +
    // -------------------------------------------------------------------------
 +
    var ftntTitles = [];                                                            // Array to hold the contents of in-place notes.
 +
    var ftntReferenced = [];                                                    // Referenced footnotes
 +
    var ftntStandalone = [];                                                    // Standalone footnotes
 +
    var ftntLabels = [];                                                        // Array to save footnote labels during processing
 +
    for (var found = true; found; ) {
 +
         found = false;
 +
         lines = lines.replace(
 +
          /\((\d+|[٠١٢٣٤٥٦٧٨٩]+|[*+])\)([\s\S]*?)\n? *\(\1([^)][\s\S]*?)\1\)/, // Find a parenthesized label, e.g., "(1)", and the first matching "(1 delimited text 1)"
 +
            function(match, label, textBetween, content) {
 +
                found = true;
 +
                if (label === '+') {                                            // A '+' label is reserved for in-place notes displayed as titles
 +
                    ftntTitles.push(content.trim());
 +
                    return titlePlcHldr + textBetween;
 +
                }
 +
                else {                                                          // A regular referenced footnote
 +
                    label = arabic2hindi(label);                                // Use Eastern-Arabic numbers
 +
                    ftntReferenced.push(
 +
                        '<p>(' + label + ') ' + content.trim() + '</p>'
 +
                    );
 +
                    ftntLabels.push(label);
 +
                    return ftntPlcHldr + textBetween;
 +
                }
 +
            }
 +
        );
 +
    }
 +
    // -------------------------------------------------------------------------
 +
    // Extract standalone footnotes.
 +
    // Even though they are displayed above referenced notes, we collect
 +
    // them later to give the user the chance to include referenced footnotes
 +
    // content within the standalone footnotes
 +
    // -------------------------------------------------------------------------
 +
    lines = lines.replace(
 +
        /\n? *\(!([\s\S]*?)!\) */g,                                          // We remove all preceding white space, including a single line break, if any
 +
        function(match, content) {
 +
            if (content) {                                                      // To support empty comments placeholders by users
 +
                content = content.trim();                                      // Remove surrounding white space after removing the delimiters
 +
                if (!/<p/.test(content)) {                                      // Loose text?
 +
                    content = '<p>' + content + '</p>';                        // Enclose in a paragraph container
 +
                }
 +
                ftntStandalone.push(content);                                  // Append to footnotes
 +
            }
 +
            return '';
 +
        }
 +
    );
 +
    var footnotes = ftntStandalone.join('') + ftntReferenced.join('');
 +
    // -------------------------------------------------------------------------
 +
    // Now we proceed to process the lines
 +
    // -------------------------------------------------------------------------
 +
    lines = (
 +
        lines
 
         .replace(/<\/p><p>/gi, '<br>\n')                                        // Undo wiki removal of single empty lines
 
         .replace(/<\/p><p>/gi, '<br>\n')                                        // Undo wiki removal of single empty lines
         .replace(/\s*(<p[^>]*>|<div[^>]*>|<pre[^>]*>)/gi, '')                   // Remove whitespace before opening container tags and the tags
+
         .replace(/\s*<(p|div|pre)[^>]*>/gi, '')                                 // Remove opening container tags and preceding whitespace
         .replace(/<\/p>|<\/div>|<\/pre>/gi, '')                                 // Remove closing tags
+
         .replace(/<\/(p|div|pre)>/gi, '')                                       // Remove closing tags
         .replace(/\s*<hr[^>]*>\s*/gi, '\n----\n')
+
         .replace(/\s*<hr[^>]*>\s*/gi, '\n----\n')                               // Replace <hr> in wiki with our symbol for an <hr>
         .split('\n')                                                           // Break into lines, assuming one beit per line
+
         .replace(/<br>/gi, '\n')                                               // Replace line break tags with line-break characters
 +
        .replace(/^ *(\..*\.) *$/gm, '$1')                                      // Trim spaces around centered lines to avoid confusion with indentation
 +
        .replace(/^ *(!.*)$/gm, '$1')                                          // Trim spaces in the beginning of comment lines ~ ~
 +
        .replace(
 +
            /\n {2,}(\S)/g,                                                    // Remaining indented lines?
 +
            function(match,char) { staggered = true; return '  ' + char; }      // Merge with preceding half, and declare staggered as true
 +
        )
 +
        .split(/\r?\n/)                                                        // Break into lines, assuming one beit per line
 
     );
 
     );
 
     lines = arrayTrim(lines);                                                  // Remove leading and trailing empty lines so that the enclosing <div> tag may be placed in separate lines
 
     lines = arrayTrim(lines);                                                  // Remove leading and trailing empty lines so that the enclosing <div> tag may be placed in separate lines
     var formattedLines = [];
+
     var formattedLines = [];                                                   // Array to hold formatted lines
    var tagPlaceholder = '<a><i></i></a>';                                      // Unlikely to be present and does not affect text size
+
    var tags = [];                                                              // To store extracted tags
+
    var staggered = false;
+
 
     for (var i = 0; i < lines.length; i++) {
 
     for (var i = 0; i < lines.length; i++) {
         var line = lines[i];
+
         var line = lines[i].trim();                                             // Remove white space around
        var indented = line.match(/^ {2,}/);                                   // Starts with two or more spaces?
+
         var nParts = 0;                                                       // Number of parts
         if (indented) staggered = true;                                         // Once is enough!
+
         if (line === "") {                                                     // Empty line?
        line = line.trim();                                                    // Remove leading and trailing spaces
+
             formattedLines.push('<br>');                                        // Replace by a line break
         if (line === "" || line.match(/<br>/)) {                               // Empty line
+
             continue;                                                          // Next!
             formattedLines.push('<br>');                                        // is replaced by a line break
+
             continue;                                                          // Done with this line
+
 
         }
 
         }
 
         if (line === "----") {                                                  // Separator line
 
         if (line === "----") {                                                  // Separator line
             formattedLines.push('<hr style="width:%bWidth%px">');
+
             formattedLines.push('<hr style="width:%hWidth%px">');               // Insert a horizontal break formatted to the width of verses
             continue;                                                          // Done with this line
+
             continue;                                                          // Next!
 
         }
 
         }
 
         if (line.match(/^!/)) {                                                // A line starting with an exclamation mark is a comment
 
         if (line.match(/^!/)) {                                                // A line starting with an exclamation mark is a comment
 
             formattedLines.push(
 
             formattedLines.push(
                 '<p class="bComment">'
+
                 '<p class="bComment">' + line.substring(1) + '</p>'             // Remove the exclamation mark and enclose in a comment paragraph
                + line.substring(1) +                                           // Remove the exclamation mark
+
                '</p>'
+
 
             );
 
             );
             continue;                                                          // Done with this line
+
             continue;                                                          // Next!
 
         }
 
         }
        var closingHalf = false;
 
 
         if (line.startsWith('..') && line.endsWith('..')) {                    // Lines delimited by two dots are treated as closing halves.
 
         if (line.startsWith('..') && line.endsWith('..')) {                    // Lines delimited by two dots are treated as closing halves.
             line = line.substring(2, line.length - 2).trim();                  // Remove delimiters and any white space
+
             line = (
            line = '*  ' + line.replace(/(\s+)/g, ' ');                        // Insert a minimal-width dummy half, and remove gaps, if any, to process as two halves
+
                line
             closingHalf = true;
+
                .substring(2, line.length - 2)                                 // Remove delimiters
             indented = false;
+
                .trim()                                                         // Remove white space that may offset delimiters
 +
                .replace(/\s{2,}/g, ' ')                                       // Remove multiple spaces to ensure processing as a single part
 +
             );
 +
             nParts++;                                                           // To process as a half or two rather than a one
 
         }
 
         }
         if (line.startsWith('.') && line.endsWith('.')) {                      // Lines delimited by dots are centered.
+
         if (line.startsWith('.') && line.endsWith('.')) {                      // Excluding the two-dots, lines delimited by dots are centered.
 
             line = line.substring(1, line.length - 1).trim();                  // Remove delimiters and any white space
 
             line = line.substring(1, line.length - 1).trim();                  // Remove delimiters and any white space
 
             pWidth[0] = Math.max(pWidth[0], measureTextWidth(line));
 
             pWidth[0] = Math.max(pWidth[0], measureTextWidth(line));
سطر ١١٥: سطر ١٩٤:
 
                 + '</p>'
 
                 + '</p>'
 
             );
 
             );
             continue;                                                          // Done with this line
+
             continue;                                                          // Next
 
         }
 
         }
 
         // ---------------------------------------------------------------------
 
         // ---------------------------------------------------------------------
         // From here we assume a regular classic column poem
+
         // Now we are left with actual lines of the poem
 
         // ---------------------------------------------------------------------
 
         // ---------------------------------------------------------------------
         // Distribute tags over words so that they can be split into parts,
+
         // Collect inline tags and replace with all-symbols
        // then save tags and replace them with no-space placeholders
+
        // placeholders to enable correct splitting of prefixes and suffixes
 
         // ---------------------------------------------------------------------
 
         // ---------------------------------------------------------------------
 +
        var tags = [];
 
         line = line.replace(
 
         line = line.replace(
             /(<([abi])(?:\s[^>]*)?>)(.*?)<\/\2>/gi,                           // Match the <a/i/b> tags and capture parameters into positional variables
+
             /<\/([abius])>/gi,                                                 // Find closing <a/i/b/u/s> tags, this gives the right order for nested tags
             function(match, openingTag, tagName, content) {                     // Compute replacements based on captured parameters
+
             function(match, tag) {
                 return content                                                  // Content inside tag
+
                 return '</' + (tags.push(tag) - 1) + '>';
                .split(/(\s+)/)                                                // Split at white space and include white spaces in the list
+
                .map(function(item) {
+
                    if (/\S/.test(item)) {                                      // If an item is non-white space
+
                        tags.push(openingTag, '</' + tagName + '>');           // Save opening and closing tags
+
                        item = tagPlaceholder + item + tagPlaceholder;          // Enclose text item in a dummy tag placeholder
+
                    }
+
                    return item;                                                // Whitespace is returned as is
+
                }).join('');                                                    // Join all items back together
+
 
             }
 
             }
 
         );
 
         );
         // ---------------------------------------------------------
+
         var tagsOpen = tags.map(function(tag, index) {                          // Iterate to find matching opening tags
        // Extract trailing symbols to align at "قافية"
+
            var tagOpen = '';
        // ---------------------------------------------------------
+
            var placeholder = '<' + index + '>';
        var trailingSymbols = '';                                               // Default to none
+
            line = line.replace(
        var leadingGap = '';                                                   // To balance trailingSymbols
+
                new RegExp('<' + tag + '(?:\\s[^>]*)?>', 'i'),                 // First occurrence of a matching opening tag
        line = line.replace(
+
                 function(match) {
            /^(.*?[\u0620-\u064Aa-zA-Z])([^\u0620-\u064Aa-zA-Z]*)$/,           // Match trailing non-letter symbols and capture parameters
+
                     tagOpen = match;                                            // Capture the matching tag
            function(match, alphabeticPart, nonAlphabeticPart) {                // A function to handle parameters
+
                     return placeholder;                                         // This return is to replace the tag placeholder in the line text
                 if (match.endsWith(tagPlaceholder)) return match;              // Too complex to handle, may consider later
+
                if (nonAlphabeticPart) {                                       // If there are trailing symbols
+
                     trailingSymbols = (
+
                        '<span class="bTrailingSymbols">'                      // Enclose in a span element to enable separate formatting, if desirable
+
                        + nonAlphabeticPart +
+
                        '</span>'
+
                     );
+
                    leadingGap = '<span class="bLeadingGap"></span>';          // A gap to balance the with of trailing symbols
+
 
                 }
 
                 }
                return alphabeticPart;                                         // Return alphabetic part to replace the original line
+
            );
            }
+
            return tagOpen;                                                     // This return is for inserting the matching opening tag in the list
         );
+
         });
 
         // ---------------------------------------------------------------------
 
         // ---------------------------------------------------------------------
 
         // Split parts
 
         // Split parts
 
         // ---------------------------------------------------------------------
 
         // ---------------------------------------------------------------------
         var nParts = 0;                                                        // Number of parts
+
         line = (
        var partWidth = 0;                                                      // Maximum part length
+
        var parts = (
+
 
             line
 
             line
             .split(/(\s{2,})/)                                                 // Split at two or more spaces and include white spaces in the list
+
             .split(/\s{2,}/)                                                   // Split parts at two or more spaces
            .map(function(item) {
+
            // -----------------------------------------------------------------
                if (/\S/.test(item)) {                                         // An item containing non-space contents is treated as a part
+
            // For each part:
                     nParts++;                                                   // Increment number of parts to count this one
+
            // -----------------------------------------------------------------
                    // ---------------------------------------------------------
+
            .map(function(part) {
                    // Enclose words in spans to enable justification
+
                var elements = (
                    // ---------------------------------------------------------
+
                    part
                    item = (
+
                    .replace(/</g, ' <').replace(/>/g, '> ')                    // Offset tags from words to isolate words
                         item
+
                    .match(                                                    // Split into three elements:
                         .split(/(\s+)/)                                         // Split at white space and include white spaces in the list
+
                        /^([^\u0620-\u064Aa-zA-Z]*)(.*?[\u0620-\u064Aa-zA-Z])([^\u0620-\u064Aa-zA-Z]*)$/
 +
                        /*(  prefix of symbols  )(    body of alphabets  )(  suffix of symbols  )*/
 +
                    )
 +
                );
 +
                elements.shift();                                              // Pop the first entry, which matches the whole regex
 +
                // -------------------------------------------------------------
 +
                // Enclose words in spans to enable justification
 +
                // -------------------------------------------------------------
 +
                elements[1] = (                                                // The body
 +
                    elements[1]
 +
                    .split(/\s+/)                                              // Split at white spaces
 +
                    .map(function(item) {
 +
                        if (/^<.*>$/.test(item)) { return item; }              // Return tag placeholders as is
 +
                        return '<span>' + item + '</span>';                    // Enclose each word in a span to enable justification
 +
                     })
 +
                    .join(' ')                                                  // Join all items back
 +
                    .replace(
 +
                        /(<\/span>)\s*(<[+*]>)/g, '$2$1'                        // Move superscripts inside word spans
 +
                    )
 +
                );
 +
                return elements.join(' ' + elementSeparator + ' ');             // A separator mark between elements, with spaces to offset from words
 +
            })
 +
            .join(' ' + partSeparator + ' ')                                    // A separator mark between parts
 +
        );
 +
        // ---------------------------------------------------------------------
 +
        // Distribute tags over words so that they can be split properly
 +
        // between parts/elements
 +
        // The order we collected the tags makes the innermost nested tags
 +
        // processed first, hence nest them properly
 +
        // ---------------------------------------------------------------------
 +
        tags.map(function(tag, i) {                                            // For the ith tag ..
 +
            line = line.replace(
 +
                new RegExp('<' + i + '>(.*)</' + i + '>'),                    // Find the enclosed text
 +
                function(match, content) {
 +
                    return (
 +
                         content                                                // Content inside tag
 +
                         .trim()                                                // Remove leading/trailing spaces
 +
                        .split(/\s+/)                                           // Split at white space and include white spaces in the list
 
                         .map(function(item) {
 
                         .map(function(item) {
                             if (
+
                             if (/<[-=]>/.test(item)) {
                                /\S/.test(item)
+
                                 return ' ' + item + ' ';                       // Return part/element separators as is, but keep spaces around
                                && !item.startsWith(tagPlaceholder)
+
                            ) {                                                 // If an item is non-white space and not already in a tag
+
                                 return '<span>' + item + '</span>';             // Enclose in a span to enable justification
+
 
                             }
 
                             }
                             return item;                                       // Return whitespace and tagged items as is
+
                             return '<' + i + '>' + item + '</' + i + '>';       // Enclose contiguous non-white space in tag
 
                         })
 
                         })
                         .join('')                                               // Join all items back together
+
                         .join(' ')                                             // Join all items back together
 
                     );
 
                     );
                    // ---------------------------------------------------------
 
                    partWidth = Math.max(
 
                        partWidth, measureTextWidth(item, fontStyle)            // Update longest part length
 
                    );
 
                    return (
 
                        '<span class="bJustified" style="width:%pWidth%px">'
 
                        + item +
 
                        '</span>'
 
                    );
 
                }
 
                else {                                                          // White space separating parts
 
                    return '<span class="bGap">  </span>';                      // Enclose in a span, typically stylized to preserve spaces
 
 
                 }
 
                 }
 +
            );
 +
        });
 +
        // ---------------------------------------------------------------------
 +
        // Restore tags
 +
        // ---------------------------------------------------------------------
 +
        tags.map(function(tag, i) {                                            // For the ith tag ..
 +
            line = (
 +
                line
 +
                .replace(new RegExp('<' + i + '>', 'g'), tagsOpen[i])
 +
                .replace(new RegExp('<\/' + i + '>', 'g'), '</' + tag + '>')
 +
            );
 +
        });
 +
        // ---------------------------------------------------------------------
 +
        // Now split again to format parts
 +
        // ---------------------------------------------------------------------
 +
        var partWidth = 0;                                                      // Maximum part length
 +
        line = (
 +
            line
 +
            .split(' ' + partSeparator + ' ')
 +
            .map(function(part) {
 +
                nParts++;                                                      // Increment number of parts to count this one
 +
                part = (
 +
                    part
 +
//                    .trim()                                                    // Remove leading/trailing spaces
 +
                    .replace(
 +
                        new RegExp(escRegExp(titlePlcHldr), "g"),
 +
                        '<sup>+</sup>'                                          // Restore the unified title label
 +
                    )
 +
                    .replace(
 +
                        new RegExp(escRegExp(ftntPlcHldr), "g"),
 +
                        function(match) {
 +
                            return '<sup>' + ftntLabels.shift() + '</sup>';    // Restore the label and dispense with it
 +
                        }
 +
                    )
 +
                );
 +
                var elements = part.split(' ' + elementSeparator + ' ');
 +
                elements[1] = elements[1].replace(
 +
                  /(<\/span>)\s*(<sup>.*?<\/sup>)/g, '$2$1'                    // Move superscripts inside word spans
 +
                );
 +
                // ---------------------------------------------------------
 +
                partWidth = Math.max(
 +
                    partWidth, measureTextWidth(elements[1], fontStyle)        // Update longest part length
 +
                );
 +
                return (
 +
                    '<span class="bPrefix">' + elements[0] + '</span>' +
 +
                    '<span class="bJustified" style="width:%pWidth%px">'
 +
                    + elements[1] +
 +
                    '</span>' +
 +
                    '<span class="bSuffix">' + elements[2] + '</span>'
 +
                );
 
             })
 
             })
 +
            .join('<=>')
 +
            .replace(/%pWidth%/g, '%pWidth' + nParts + '%')                    // The number of parts is known now
 
         );
 
         );
 
         pWidth[nParts] = Math.max(pWidth[nParts], partWidth);
 
         pWidth[nParts] = Math.max(pWidth[nParts], partWidth);
        line = (
+
         formattedLines.push('<p class="beit">' + line + '</p>');
            closingHalf ?
+
            parts[2] :
+
            '<span class="bJustified" style="width:%bWidth%px">'
+
                + parts.join('')
+
                + '</span>'
+
        ).replace(/%pWidth%/g, '%pWidth' + nParts + '%');                      // The number of parts is known now
+
        if (indented) { line = '%margin%' + line; }
+
        else          { line = line + '%margin%'; }
+
         formattedLines.push(
+
            '<p class="beit">'
+
            + leadingGap
+
            + line
+
            + trailingSymbols
+
            + '</p>'
+
        );
+
 
     }
 
     }
 
     for (var i = 0; i < pWidth.length; i++) {
 
     for (var i = 0; i < pWidth.length; i++) {
سطر ٢٢٥: سطر ٣٤٦:
 
         return (bWidth - nGaps[i] * bGap) / (i ? i : 1);
 
         return (bWidth - nGaps[i] * bGap) / (i ? i : 1);
 
     });
 
     });
     var margin = (
+
    if (bWidth > bWidthMax) { staggered = true; }                              // Split too long parts over rows
         staggered ?
+
     var actualPartSeparator = (
         '<span class="bGap" style="width:' + bGap + 'px"> </span>' :
+
         staggered?
         ''                                                                     // Default to none
+
         '<span class="bStaggerOffset"></span><br>' +                           // Add an offsetting space after first half
 +
        '<span class="bStaggerOffset">   </span>'                             // and before second half, and include staggering spaces
 +
         : '<span class="bGap">    <\/span>'                                     // Otherwise, make a fixed-width gap with white spaces
 
     );
 
     );
     var tagIndex = 0;                                                           // To track the current tag being replaced
+
     var hWidth = staggered ? pWidth[2] + bStaggerOffset : bWidth;               // Horizontal line width, default beit width, but only single part width when staggered
     return (
+
     var formattedPoem = (
         formattedLines
+
         '<div class="poemBody">' +
        .join('')                                                               // Concatenate
+
            formattedLines
        .replace(new RegExp(tagPlaceholder, "g"), function() {                  // Restore saved tags
+
            .join('')                                                           // Concatenate
             return tags[tagIndex++];
+
            .replace(/<=>/g, actualPartSeparator)
        })
+
             .replace(/%pWidth(\d+)%/g, function(match, i) {                     // Replace computed part widths
        .replace(/%pWidth(\d+)%/g, function(match, i) {                         // Replace computed part widths
+
                return pWidth[i].toFixed(1);
            return pWidth[i].toFixed(1);
+
            })
        })
+
            .replace(/\s*<sup>/g, '<sup>')                                      // Stick notes labels to preceding text
        .replace(/%bWidth%/g, bWidth)                                           // Replace computed overall beit width
+
            .replace(/\s*<sup>\+<\/sup>/g, function() {                        // Restore saved titles
        .replace(/%margin%/g, margin)
+
                return '<sup title="' + ftntTitles.shift() + '">+</sup>';
 +
            })
 +
            .replace(/%bWidth%/g, bWidth)                                       // Replace computed overall beit width
 +
            .replace(/%hWidth%/g, hWidth)                                       // Replace computed overall beit width
 +
        + '</div>'
 
     );
 
     );
 +
    if (footnotes) {
 +
        formattedPoem += '<div class="poemGutter">' + footnotes + '</div>'
 +
    }
 +
    return formattedPoem;
 
}
 
}
  

مراجعة ٠٣:٢٩، ٨ مارس ٢٠٢٤

/* الجافاسكريبت الموضوع هنا سيتم تحميله لكل المستخدمين مع كل تحميل للصفحة. */
 
// =============================================================================
// Formate a poem
// 2024-02-27: Created by Abdalla G. M. Ahmed
// 2024-03-07: Updated to include annotation
// =============================================================================
 
// -----------------------------------------------------------------------------
// Measure displayed width of an HTML text
// -----------------------------------------------------------------------------
 
function measureTextWidth(text, font) {
    var tmpSpan = document.createElement("span");
    tmpSpan.style.font = font;                                                  // Apply the font style
    tmpSpan.style.position = "absolute";
    tmpSpan.style.visibility = "hidden";
    tmpSpan.style.whiteSpace = "nowrap";
    tmpSpan.innerHTML = text;
 
    document.body.appendChild(tmpSpan);
    var textWidth = tmpSpan.offsetWidth;
    document.body.removeChild(tmpSpan);
 
    return textWidth;
}
 
// -----------------------------------------------------------------------------
// Retrieve font specs of an element
// -----------------------------------------------------------------------------
 
function getFontStyle(node) {
    var computedStyle = window.getComputedStyle(node);
    var fontSize = computedStyle.fontSize;
    var fontFamily = computedStyle.fontFamily;
    var fontWeight = computedStyle.fontWeight;
    var fontStyle = computedStyle.fontStyle;
    var fontVariant = computedStyle.fontVariant;
    return (
        fontStyle + " " + fontVariant + " " +
        fontWeight + " " + fontSize + " " + fontFamily
    );
}
 
// -----------------------------------------------------------------------------
// Trim empty leading and trailing entries of an array
// -----------------------------------------------------------------------------
 
function arrayTrim(arr) {
    var result = arr.slice();                                                   // Clone the array to avoid modifying the original array
    while (result.length > 0 && !result[0]) { result.shift(); }                 // Remove empty elements from the beginning
    while (result.length > 0 && !result[result.length - 1]) { result.pop(); }   // Remove empty elements from the end
    return result;
}
 
// -----------------------------------------------------------------------------
// Escape special characters in strings used to build RegExp
// -----------------------------------------------------------------------------
 
function escRegExp(string) {
    return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');                       // $& means the whole matched string
}
 
// -----------------------------------------------------------------------------
// Replace digits to use Hindi Arabic (٠-٩)
// -----------------------------------------------------------------------------
 
function arabic2hindi(input) {
    return input.replace(/\d/g, function(match) {
        return String.fromCharCode(parseInt(match, 10) + 0x0660);
    });
}
 
// -----------------------------------------------------------------------------
// Format contents of a container (e.g., div) as a poem
// -----------------------------------------------------------------------------
 
function poemFormat(poem) {
    const bGap = 50;                                                            // This is twice the sum of widths of bPrefix and bSuffix in CSS
    const bStaggerOffset = 75;                                                  // Should match CSS
    const bWidthMax = 450;                                                      // This should match the CSS declaration of the width of poemBody div less padding and bPrefix/bSuffix width
    const elementSeparator = '<->';                                             // A placeholder for separating identified parts during processing
    const partSeparator = '<=>';                                                // A placeholder for separating prefix/body/suffix of a part
    const titlePlcHldr = '<+>';                                                 // A placeholder for in-place footnotes, distinct from '+' characters that may exist in the text
    const ftntPlcHldr = '<*>';                                                  // A placeholder for gutter footnotes
    var lines = poem.innerHTML;                                                 // We get the HTML to retain inline tags <a,i,b,s,u>
    var bWidth = parseFloat(poem.getAttribute('data-bWidth') || 0);             // Overall width of beit
    var pWidth = [0, 0, 0, 0, 0];                                               // Widths of part for different number of parts, index 0 is used for centered verses
    var nGaps  = [2, 0, 1, 2, 3];                                               // Number of gaps for different number of parts, initially assuming no leading gaps
    var fontStyle = getFontStyle(poem);                                         // For determining the rendered widths of parts
    var staggered = false;                                                      // Whether the poem is fed as one half per line, indenting second halves
    // -------------------------------------------------------------------------
    // Extract referenced footnotes
    // -------------------------------------------------------------------------
    var ftntTitles = [];                                                            // Array to hold the contents of in-place notes.
    var ftntReferenced = [];                                                    // Referenced footnotes
    var ftntStandalone = [];                                                    // Standalone footnotes
    var ftntLabels = [];                                                        // Array to save footnote labels during processing
    for (var found = true; found; ) {
        found = false;
        lines = lines.replace(
          /\((\d+|[٠١٢٣٤٥٦٧٨٩]+|[*+])\)([\s\S]*?)\n? *\(\1([^)][\s\S]*?)\1\)/, // Find a parenthesized label, e.g., "(1)", and the first matching "(1 delimited text 1)"
            function(match, label, textBetween, content) {
                found = true;
                if (label === '+') {                                            // A '+' label is reserved for in-place notes displayed as titles
                    ftntTitles.push(content.trim());
                    return titlePlcHldr + textBetween;
                }
                else {                                                          // A regular referenced footnote
                    label = arabic2hindi(label);                                // Use Eastern-Arabic numbers
                    ftntReferenced.push(
                        '<p>(' + label + ') ' + content.trim() + '</p>'
                    );
                    ftntLabels.push(label);
                    return ftntPlcHldr + textBetween;
                }
            }
        );
    }
    // -------------------------------------------------------------------------
    // Extract standalone footnotes.
    // Even though they are displayed above referenced notes, we collect
    // them later to give the user the chance to include referenced footnotes
    // content within the standalone footnotes
    // -------------------------------------------------------------------------
    lines = lines.replace(
        /\n? *\(!([\s\S]*?)!\) */g,                                           // We remove all preceding white space, including a single line break, if any
        function(match, content) {
            if (content) {                                                      // To support empty comments placeholders by users
                content = content.trim();                                       // Remove surrounding white space after removing the delimiters
                if (!/<p/.test(content)) {                                      // Loose text?
                    content = '<p>' + content + '</p>';                         // Enclose in a paragraph container
                }
                ftntStandalone.push(content);                                   // Append to footnotes
            }
            return '';
        }
    );
    var footnotes = ftntStandalone.join('') + ftntReferenced.join('');
    // -------------------------------------------------------------------------
    // Now we proceed to process the lines
    // -------------------------------------------------------------------------
    lines = (
        lines
        .replace(/<\/p><p>/gi, '<br>\n')                                        // Undo wiki removal of single empty lines
        .replace(/\s*<(p|div|pre)[^>]*>/gi, '')                                 // Remove opening container tags and preceding whitespace
        .replace(/<\/(p|div|pre)>/gi, '')                                       // Remove closing tags
        .replace(/\s*<hr[^>]*>\s*/gi, '\n----\n')                               // Replace <hr> in wiki with our symbol for an <hr>
        .replace(/<br>/gi, '\n')                                                // Replace line break tags with line-break characters
        .replace(/^ *(\..*\.) *$/gm, '$1')                                      // Trim spaces around centered lines to avoid confusion with indentation
        .replace(/^ *(!.*)$/gm, '$1')                                          // Trim spaces in the beginning of comment lines ~ ~
        .replace(
            /\n {2,}(\S)/g,                                                     // Remaining indented lines?
            function(match,char) { staggered = true; return '  ' + char; }      // Merge with preceding half, and declare staggered as true
        )
        .split(/\r?\n/)                                                         // Break into lines, assuming one beit per line
    );
    lines = arrayTrim(lines);                                                   // Remove leading and trailing empty lines so that the enclosing <div> tag may be placed in separate lines
    var formattedLines = [];                                                    // Array to hold formatted lines
    for (var i = 0; i < lines.length; i++) {
        var line = lines[i].trim();                                             // Remove white space around
        var nParts =  0;                                                        // Number of parts
        if (line === "") {                                                      // Empty line?
            formattedLines.push('<br>');                                        // Replace by a line break
            continue;                                                           // Next!
        }
        if (line === "----") {                                                  // Separator line
            formattedLines.push('<hr style="width:%hWidth%px">');               // Insert a horizontal break formatted to the width of verses
            continue;                                                           // Next!
        }
        if (line.match(/^!/)) {                                                 // A line starting with an exclamation mark is a comment
            formattedLines.push(
                '<p class="bComment">' + line.substring(1) + '</p>'             // Remove the exclamation mark and enclose in a comment paragraph
            );
            continue;                                                           // Next!
        }
        if (line.startsWith('..') && line.endsWith('..')) {                     // Lines delimited by two dots are treated as closing halves.
            line = (
                line
                .substring(2, line.length - 2)                                  // Remove delimiters
                .trim()                                                         // Remove white space that may offset delimiters
                .replace(/\s{2,}/g, ' ')                                        // Remove multiple spaces to ensure processing as a single part
            );
            nParts++;                                                           // To process as a half or two rather than a one
        }
        if (line.startsWith('.') && line.endsWith('.')) {                       // Excluding the two-dots, lines delimited by dots are centered.
            line = line.substring(1, line.length - 1).trim();                   // Remove delimiters and any white space
            pWidth[0] = Math.max(pWidth[0], measureTextWidth(line));
            formattedLines.push(
                '<p class="beit">'
                + '<span class="bJustified">'
                + line
                + '</span>'
                + '</p>'
            );
            continue;                                                           // Next
        }
        // ---------------------------------------------------------------------
        // Now we are left with actual lines of the poem
        // ---------------------------------------------------------------------
        // Collect inline tags and replace with all-symbols
        // placeholders to enable correct splitting of prefixes and suffixes
        // ---------------------------------------------------------------------
        var tags = [];
        line = line.replace(
            /<\/([abius])>/gi,                                                  // Find closing <a/i/b/u/s> tags, this gives the right order for nested tags
            function(match, tag) {
                return '</' + (tags.push(tag) - 1) + '>';
            }
        );
        var tagsOpen = tags.map(function(tag, index) {                          // Iterate to find matching opening tags
            var tagOpen = '';
            var placeholder = '<' + index + '>';
            line = line.replace(
                new RegExp('<' + tag + '(?:\\s[^>]*)?>', 'i'),                  // First occurrence of a matching opening tag
                function(match) {
                    tagOpen = match;                                            // Capture the matching tag
                    return placeholder;                                         // This return is to replace the tag placeholder in the line text
                }
            );
            return tagOpen;                                                     // This return is for inserting the matching opening tag in the list
        });
        // ---------------------------------------------------------------------
        // Split parts
        // ---------------------------------------------------------------------
        line = (
            line
            .split(/\s{2,}/)                                                    // Split parts at two or more spaces
            // -----------------------------------------------------------------
            // For each part:
            // -----------------------------------------------------------------
            .map(function(part) {
                var elements = (
                    part
                    .replace(/</g, ' <').replace(/>/g, '> ')                    // Offset tags from words to isolate words
                    .match(                                                     // Split into three elements:
                        /^([^\u0620-\u064Aa-zA-Z]*)(.*?[\u0620-\u064Aa-zA-Z])([^\u0620-\u064Aa-zA-Z]*)$/
                        /*(   prefix of symbols   )(    body of alphabets   )(   suffix of symbols   )*/
                    )
                );
                elements.shift();                                               // Pop the first entry, which matches the whole regex
                // -------------------------------------------------------------
                // Enclose words in spans to enable justification
                // -------------------------------------------------------------
                elements[1] = (                                                 // The body
                    elements[1]
                    .split(/\s+/)                                               // Split at white spaces
                    .map(function(item) {
                        if (/^<.*>$/.test(item)) { return item; }               // Return tag placeholders as is
                        return '<span>' + item + '</span>';                     // Enclose each word in a span to enable justification
                    })
                    .join(' ')                                                   // Join all items back
                    .replace(
                        /(<\/span>)\s*(<[+*]>)/g, '$2$1'                        // Move superscripts inside word spans
                    )
                );
                return elements.join(' ' + elementSeparator + ' ');             // A separator mark between elements, with spaces to offset from words
            })
            .join(' ' + partSeparator + ' ')                                    // A separator mark between parts
        );
        // ---------------------------------------------------------------------
        // Distribute tags over words so that they can be split properly
        // between parts/elements
        // The order we collected the tags makes the innermost nested tags
        // processed first, hence nest them properly
        // ---------------------------------------------------------------------
        tags.map(function(tag, i) {                                             // For the ith tag ..
            line = line.replace(
                new RegExp('<' + i + '>(.*)</' + i + '>'),                    // Find the enclosed text
                function(match, content) {
                    return (
                        content                                                 // Content inside tag
                        .trim()                                                 // Remove leading/trailing spaces
                        .split(/\s+/)                                           // Split at white space and include white spaces in the list
                        .map(function(item) {
                            if (/<[-=]>/.test(item)) {
                                return ' ' + item + ' ';                        // Return part/element separators as is, but keep spaces around
                            }
                            return '<' + i + '>' + item + '</' + i + '>';       // Enclose contiguous non-white space in tag
                        })
                        .join(' ')                                              // Join all items back together
                    );
                }
            );
        });
        // ---------------------------------------------------------------------
        // Restore tags
        // ---------------------------------------------------------------------
        tags.map(function(tag, i) {                                             // For the ith tag ..
            line = (
                line
                .replace(new RegExp('<' + i + '>', 'g'), tagsOpen[i])
                .replace(new RegExp('<\/' + i + '>', 'g'), '</' + tag + '>')
            );
        });
        // ---------------------------------------------------------------------
        // Now split again to format parts
        // ---------------------------------------------------------------------
        var partWidth = 0;                                                      // Maximum part length
        line = (
            line
            .split(' ' + partSeparator + ' ')
            .map(function(part) {
                nParts++;                                                       // Increment number of parts to count this one
                part = (
                    part
//                     .trim()                                                     // Remove leading/trailing spaces
                    .replace(
                        new RegExp(escRegExp(titlePlcHldr), "g"),
                        '<sup>+</sup>'                                          // Restore the unified title label
                    )
                    .replace(
                        new RegExp(escRegExp(ftntPlcHldr), "g"),
                        function(match) {
                            return '<sup>' + ftntLabels.shift() + '</sup>';     // Restore the label and dispense with it
                        }
                    )
                );
                var elements = part.split(' ' + elementSeparator + ' ');
                elements[1] = elements[1].replace(
                   /(<\/span>)\s*(<sup>.*?<\/sup>)/g, '$2$1'                    // Move superscripts inside word spans
                );
                // ---------------------------------------------------------
                partWidth = Math.max(
                    partWidth, measureTextWidth(elements[1], fontStyle)         // Update longest part length
                );
                return (
                    '<span class="bPrefix">' + elements[0] + '</span>' +
                    '<span class="bJustified" style="width:%pWidth%px">'
                    + elements[1] +
                    '</span>' +
                    '<span class="bSuffix">' + elements[2] + '</span>'
                );
            })
            .join('<=>')
            .replace(/%pWidth%/g, '%pWidth' + nParts + '%')                     // The number of parts is known now
        );
        pWidth[nParts] = Math.max(pWidth[nParts], partWidth);
        formattedLines.push('<p class="beit">' + line + '</p>');
    }
    for (var i = 0; i < pWidth.length; i++) {
        var bWidth_i = pWidth[i] ? pWidth[i] * i + nGaps[i] * bGap : 0;         // Width to accommodate this number/width of parts
        bWidth = Math.max(bWidth, bWidth_i);                                    // Accommodate the widest
    }
    pWidth = pWidth.map(function(w, i) {
        return (bWidth - nGaps[i] * bGap) / (i ? i : 1);
    });
    if (bWidth > bWidthMax) { staggered = true; }                               // Split too long parts over rows
    var actualPartSeparator = (
        staggered?
        '<span class="bStaggerOffset"></span><br>' +                            // Add an offsetting space after first half
        '<span class="bStaggerOffset">    </span>'                              // and before second half, and include staggering spaces
        : '<span class="bGap">    <\/span>'                                     // Otherwise, make a fixed-width gap with white spaces
    );
    var hWidth = staggered ? pWidth[2] + bStaggerOffset : bWidth;               // Horizontal line width, default beit width, but only single part width when staggered
    var formattedPoem = (
        '<div class="poemBody">' +
            formattedLines
            .join('')                                                           // Concatenate
            .replace(/<=>/g, actualPartSeparator)
            .replace(/%pWidth(\d+)%/g, function(match, i) {                     // Replace computed part widths
                return pWidth[i].toFixed(1);
            })
            .replace(/\s*<sup>/g, '<sup>')                                      // Stick notes labels to preceding text
            .replace(/\s*<sup>\+<\/sup>/g, function() {                         // Restore saved titles
                return '<sup title="' + ftntTitles.shift() + '">+</sup>';
            })
            .replace(/%bWidth%/g, bWidth)                                       // Replace computed overall beit width
            .replace(/%hWidth%/g, hWidth)                                       // Replace computed overall beit width
        + '</div>'
    );
    if (footnotes) {
        formattedPoem += '<div class="poemGutter">' + footnotes + '</div>'
    }
    return formattedPoem;
}
 
$(function () {
    document.querySelectorAll('.ash3ar').forEach(function(poems) {              // Divisions declared as lists of أشعار
        poems.innerHTML = (                                                     // Replace their content with ..
            poems.innerHTML                                                     // Take innerHTML to preserve formatting
            .split(/\+{3,}/)                                                    // Use three or more "+" characters to split
            .map(function(txt) { return '<div class="abyat">'+txt+'</div>'; })  // Enclose in a class declared as abyat for subsequent identification
            .join('\n<hr>\n')                                                           
        );
    });
 
    document.querySelectorAll('.abyat').forEach(function(poem) {                // Iterate through containers declared as أبيات
        poem.innerHTML = poemFormat(poem);                                      // Process to format
    });
 
    var toggler = document.getElementsByClassName("treeBranch");
    var i;
 
    for (i = 0; i < toggler.length; i++) {
        toggler[i].addEventListener("click", function() {
            this.parentElement.querySelector(".treeBranch-nested").classList.toggle("treeBranch-active");
            this.classList.toggle("treeBranch-down");
        });
    }
}());