Extract chords from a tab using PHP

I am losing my hair trying to figure out how to analyze the music (text) tab using preg_match_all and PREG_OFFSET_CAPTURE.

An example :

[D#] [G#] [Fm] 
[C#] [Fm] [C#] [Fm] [C#] [Fm] 

[C]La la la la la la [Fm]la la la la [D#]

[Fm]I made this song Cause I [Bbm]love you 
[C]I made this song just for [Fm]you [D#]
[Fm]I made this song deep in [Bbm]my heart

The result that I am trying to get is:

D# G# Fm 
C# Fm C# Fm C# Fm 

C                 Fm          D#
La la la la la la la la la la

Fm                       Bbm     
I made this song Cause I love you 

C                     Fm  D#
I made this song just for you 

Fm                       Bbm
I made this song deep in my heart

And finally, I want to wrap the chords with html tags.

Note that the spaces between chords must exactly match the position of these chords in the original input.

I started parsing line-by-line input, finding chords, getting my position, but my code doesn’t work ... There is something wrong with my line_extract_chords function , it doesn’t work as it should.

Any ideas?

<style>
body{
        font-family: monospace;
        white-space: pre;
</style>

<?php 

function parse_song($content){
    $lines = explode(PHP_EOL, $content); //explode lines

    foreach($lines as $key=>$line){
        $chords_line = line_extract_chords($line);
        $lines[$key] = implode("\n\r",(array)$chords_line);
    }

    return implode("\n\r",$lines);
}

function line_extract_chords($line){

    $line_chords = null; //text line with chords, used to compute offsets
    $line_chords_html = null; //line with chords links
    $found_chords = array();

    $line = html_entity_decode($line); //remove special characters (would make offset problems)

    preg_match_all("/\[([^\]]*)\]/", $line, $matches, PREG_OFFSET_CAPTURE);

    $chord_matches = array();

    if ( $matches[1] ){
        foreach($matches[1] as $key=>$chord_match){

            $chord = $chord_match[0];


            $position = $chord_match[1];
            $offset= $position;
            $offset-= 1; //left bracket
            $offset-=strlen($line_chords); //already filled line

            //previous matches
            if ($found_chords){
                $offset -= strlen(implode('',$found_chords));
                $offset -= 2*(count($found_chords)); //brackets for previous chords
            }

            $chord_html = '<a href="#">'.$chord.'</a>';

            //add spaces
            if ($offset>0){
                $line_chords.= str_repeat(" ", $offset);
                $line_chords_html.= str_repeat(" ", $offset);
            }

            $line_chords.=$chord;
            $line_chords_html.=$chord_html;
            $found_chords[] = $chord;

        }

    }

    $line = htmlentities($line); //revert html_entity_decode()

    if ($line_chords){
        $line = preg_replace('/\[([^\]]*)\]/', '', $line);
        return array($line_chords_html,$line);
    }else{
        return $line;
    }

}
?>
+4
source share
2

. , , .

<style>
.line{
    font-family: monospace;
    white-space: pre;
    margin-bottom:0.75rem;
}

.group{
    display: inline-block;
    margin-right: 0.5rem;
}
.group .top,
.group .top{
    display: block;
}
</style>
<?php

$input = "[D#] [G#] [Fm] 
[C#] [Fm] [C#] [Fm] [C#] [Fm] 

[C]La la la la la la [Fm]la la la la [D#]

[Fm]I made this song Cause I [Bbm]love you 
[C]I made this song just for [Fm]you [D#]
[Fm]I made this song deep in [Bbm]my heart";

$output = '';

$inputLines = explode(PHP_EOL,$input);

foreach($inputLines as $line){
    $output .='<div class="line">';

    if (!strlen($line)){
        $output .= '&nbsp;';
    }
    else{
        $inputWords = explode(' ',$line);

        foreach($inputWords as $word){
            if (preg_match('/^\[(.+)\](.+)$/', $word, $parts)){
                $output .='<span class="group"><span class="top">'.$parts[1].'</span><span class="bottom">'.$parts[2].'</span></span>';
            }
            elseif(preg_match('/^\[(.+)\]$/', $word, $parts)){
                $output .='<span class="group"><span class="top">'.$parts[1].'</span><span class="bottom">&nbsp;</span></span>';
            }
            else{
                $output .='<span class="group"><span class="top">&nbsp;</span><span class="bottom">'.$word.'</span></span>';
            }
        }
    }

    $output .='</div>';

}
die ($output);

, , . script , HTML. CSS.

, , . Fm D# 5 . , .

ADD:

.

, . , , . . , , :

  • HTML
  • . .

, ?

  • ( ) , . white-space: pre; , .
  • (font-family: monospace;), , .

:

<style>
body{
        font-family: monospace;
        white-space: pre;
</style>

<?php 


function parse_song($content){
    $lines = explode(PHP_EOL, $content); //explode lines

    foreach($lines as $key=>$line){
        $chords_line = line_extract_chords($line);
        $lines[$key] = implode("\n\r",(array)$chords_line);
    }

    return implode("\n\r",$lines);
}

function line_extract_chords($line){

    $line_chords = null; //text line with chords, used to compute offsets
    $line_chords_html = null; //line with chords links
    $found_chords = array();

    $line = html_entity_decode($line); //remove special characters (would make offset problems)

    preg_match_all("/\[([^\]]*)\]/", $line, $matches, PREG_OFFSET_CAPTURE);

    $chord_matches = array();

    if ( $matches[1] ){
        foreach($matches[1] as $key=>$chord_match){

            $chord = $chord_match[0];


            $position = $chord_match[1];
            $offset= $position;
            $offset-= 1; //left bracket
            $offset-=strlen($line_chords); //already filled line

            //previous matches
            if ($found_chords){
                $offset -= strlen(implode('',$found_chords));
                $offset -= 2*(count($found_chords)); //brackets for previous chords
            }

            $chord_html = '<a href="#">'.$chord.'</a>';

            //add spaces
            if ($offset>0){
                $line_chords.= str_repeat(" ", $offset);
                $line_chords_html.= str_repeat(" ", $offset);
            }

            $line_chords.=$chord;
            $line_chords_html.=$chord_html;
            $found_chords[] = $chord;

        }

    }

    $line = htmlentities($line); //revert html_entity_decode()

    if ($line_chords){
        $line = preg_replace('/\[([^\]]*)\]/', '', $line);
        return array($line_chords_html,$line);
    }else{
        return $line;
    }

}

$input = "[D#] [G#] [Fm] 
[C#] [Fm] [C#] [Fm] [C#] [Fm] 

[C]La la la la la la [Fm]la la la la [D#]

[Fm]I made this song Cause I [Bbm]love you 
[C]I made this song just for [Fm]you [D#]
[Fm]I made this song deep in [Bbm]my heart";



die(parse_song($input));

self::, .

, . .

, , (, ) . . HTML XML JSON, , . .

. .

+3

, - , , .

<style>
.ugs-song{
    font-family: monospace;
    white-space: pre;
    margin-bottom:0.75rem;
}

.ugs-song-line-chunk{
    display: inline-block;
}
.ugs-song-line-chunk .top,
.ugs-song-line-chunk .bottom{
    display: block;
}
</style>

<?php

function parse_song($content){

    $input_lines = explode(PHP_EOL, $content); //explode lines

    $chunks_pattern = '~ \h*
    (?|        # open a "branch reset group"
        ( \[ [^]]+ ] (?: \h* \[ [^]]+ ] )*+ ) # one or more chords in capture group 1

        ( [^[]* (?<=) )  # eventual lyrics (group 2)
      |                      # OR
        ()                   # no chords (group 1)
        ( [^[]* [^[] )   # lyrics (group 2)
    )          # close the "branch reset group"
    ~x';

    $chords_pattern = '/\[([^]]*)\]/';

    //get line chunks
    $all_lines_chunks = null;

    foreach ((array)$input_lines as $key=>$input_line){
        if (preg_match_all($chunks_pattern, $input_line, $matches, PREG_SET_ORDER)) {
            $all_lines_chunks[$key] = array_map(function($i) { return [$i[1], $i[2]]; }, $matches);
        }
    }

    foreach ((array)$all_lines_chunks as $key=>$line_chunks){
        $line_html = null;

        foreach ((array)$line_chunks as $key=>$single_line_chunk){

            $chords_html = null;
            $words_html = null;

            if ($chords_content = $single_line_chunk[0]){

                if (preg_match_all($chords_pattern, $chords_content, $matches, PREG_SET_ORDER)) {

                    $chords_content = null; //reset it

                    foreach ((array)$matches as $match){
                        $chord_str = $match[1];
                        $chords_content.= sprintf('<a class="ugs-song-chord" href="#">%s</a>',$chord_str);



                    }
                }
            }

            if (!$chords_content) $chords_content = "&nbsp;"; //force content if empty !
            $chords_html = sprintf('<span class="top">%s</span>',$chords_content);


            if (!$words_content = $single_line_chunk[1]) $words_content = "&nbsp;"; //force content if empty !
            $words_content = preg_replace('/\s(?=\S*$)/',"&nbsp;",$words_content); //replace last space by non-breaking space (span would trim a regular space)


            $words_html = sprintf('<span class="bottom">%s</span>',$words_content);

            $line_html.= sprintf('<div class="ugs-song-chunk">%s</div>',$chords_html.$words_html);
        }

        $all_lines_html[]=sprintf('<div class="ugs-song-line">%s</div>',$line_html);
    }

    return implode(PHP_EOL,$all_lines_html);

}

$input = "[C]Hush me, tou[C]ch me
[Gm]Perfume, the wind and the lea[C]ves
[C]Hush me, tou[C]ch me
[Gm]The burns, the holes in the she[C]ets";

echo parse_song($input);
?>
+1

Source: https://habr.com/ru/post/1616301/


All Articles