fortplot_format_parser.f90 Source File


Source Code

module fortplot_format_parser
    implicit none
    
    private
    
    public :: parse_format_string, contains_format_chars
    
contains

    subroutine parse_format_string(format_str, marker, linestyle)
        character(len=*), intent(in) :: format_str
        character(len=*), intent(out) :: marker, linestyle
        
        character(len=20) :: trimmed_str
        
        ! Initialize outputs
        marker = ''
        linestyle = ''
        
        trimmed_str = trim(adjustl(format_str))
        
        ! Handle empty or whitespace strings
        if (len_trim(trimmed_str) == 0) then
            return
        end if
        
        ! Check for named linestyles first  
        if (translate_named_linestyle(trimmed_str, linestyle)) then
            marker = ''  ! Named linestyles have no markers
            return
        end if
        
        ! Check for complete linestyle patterns first
        if (extract_linestyle_pattern(trimmed_str, linestyle)) then
            ! Extract any remaining marker
            call extract_marker_from_remaining(trimmed_str, linestyle, marker)
        else
            ! No linestyle pattern found, check for marker only
            call extract_marker_only(trimmed_str, marker)
            if (len_trim(marker) > 0) then
                linestyle = 'None'
            end if
        end if
    end subroutine

    pure function is_marker_char(char) result(is_marker)
        character(len=1), intent(in) :: char
        logical :: is_marker
        
        ! Common matplotlib markers
        select case (char)
        case ('o', 'x', '+', '*', 's', '^', 'v', '<', '>', 'd', 'D', 'p', 'h', 'H')
            is_marker = .true.
        case default
            is_marker = .false.
        end select
    end function

    pure function contains_format_chars(style_str) result(has_format)
        !! Check if a style string contains matplotlib format characters
        !! Returns false for named linestyles (solid, dashed, etc)
        character(len=*), intent(in) :: style_str
        logical :: has_format
        
        character(len=20) :: trimmed_str
        
        has_format = .false.
        trimmed_str = trim(adjustl(style_str))
        
        ! Empty string has no format
        if (len_trim(trimmed_str) == 0) then
            return
        end if
        
        ! Named linestyles don't count as format strings
        if (is_named_linestyle(trimmed_str)) then
            has_format = .false.
            return
        end if
        
        ! Check for actual matplotlib syntax patterns
        if (has_linestyle_pattern(trimmed_str) .or. has_valid_marker_syntax(trimmed_str)) then
            has_format = .true.
        end if
    end function

    function translate_named_linestyle(name_str, linestyle) result(translated)
        !! Translate named linestyle to matplotlib syntax
        character(len=*), intent(in) :: name_str
        character(len=*), intent(out) :: linestyle
        logical :: translated
        
        select case (trim(name_str))
        case ('solid')
            linestyle = '-'
            translated = .true.
        case ('dashed')
            linestyle = '--'
            translated = .true.
        case ('dotted')
            linestyle = ':'
            translated = .true.
        case ('dashdot')
            linestyle = '-.'
            translated = .true.
        case ('None')
            linestyle = 'None'
            translated = .true.
        case default
            linestyle = ''
            translated = .false.
        end select
    end function

    function extract_linestyle_pattern(format_str, linestyle) result(found)
        !! Extract linestyle pattern from format string
        character(len=*), intent(in) :: format_str
        character(len=*), intent(out) :: linestyle
        logical :: found
        
        found = .false.
        linestyle = ''
        
        ! Look for specific patterns in order of length (longest first)
        if (index(format_str, '--') > 0) then
            linestyle = '--'
            found = .true.
        else if (index(format_str, '-.') > 0) then
            linestyle = '-.'
            found = .true.
        else if (index(format_str, '-') > 0) then
            linestyle = '-'
            found = .true.
        else if (index(format_str, ':') > 0) then
            linestyle = ':'
            found = .true.
        else if (trim(format_str) == 'None') then
            linestyle = 'None'
            found = .true.
        end if
    end function

    subroutine extract_marker_from_remaining(format_str, linestyle, marker)
        !! Extract marker from format string after removing linestyle
        character(len=*), intent(in) :: format_str, linestyle
        character(len=*), intent(out) :: marker
        
        integer :: i
        character(len=1) :: char
        
        marker = ''
        
        ! Find first valid marker character
        do i = 1, len_trim(format_str)
            char = format_str(i:i)
            if (is_marker_char(char)) then
                marker = char
                return
            end if
        end do
    end subroutine

    subroutine extract_marker_only(format_str, marker)
        !! Extract marker when no linestyle is present
        character(len=*), intent(in) :: format_str
        character(len=*), intent(out) :: marker
        
        integer :: i
        character(len=1) :: char
        
        marker = ''
        
        ! Find first valid marker character
        do i = 1, len_trim(format_str)
            char = format_str(i:i)
            if (is_marker_char(char)) then
                marker = char
                return
            end if
        end do
    end subroutine

    pure function is_linestyle_char(char) result(is_linestyle)
        !! Check if character is part of linestyle syntax
        character(len=1), intent(in) :: char
        logical :: is_linestyle
        
        select case (char)
        case ('-', '.', ':')
            is_linestyle = .true.
        case default
            is_linestyle = .false.
        end select
    end function

    pure function is_named_linestyle(style_str) result(is_named)
        !! Check if string is a named linestyle parameter
        character(len=*), intent(in) :: style_str
        logical :: is_named
        
        select case (trim(style_str))
        case ('solid', 'dashed', 'dotted', 'dashdot', 'None')
            is_named = .true.
        case default
            is_named = .false.
        end select
    end function

    pure function has_linestyle_pattern(style_str) result(has_pattern)
        !! Check if string contains linestyle pattern characters
        character(len=*), intent(in) :: style_str
        logical :: has_pattern
        
        has_pattern = .false.
        
        ! Check for specific linestyle patterns
        if (index(style_str, '--') > 0 .or. &
            index(style_str, '-.') > 0 .or. &
            index(style_str, ':') > 0 .or. &
            (index(style_str, '-') > 0 .and. index(style_str, '--') == 0 .and. index(style_str, '-.') == 0)) then
            has_pattern = .true.
        end if
    end function

    pure function has_valid_marker_syntax(style_str) result(has_marker)
        !! Check if string contains valid standalone marker syntax
        character(len=*), intent(in) :: style_str
        logical :: has_marker
        
        integer :: i, marker_count
        character(len=1) :: char
        
        has_marker = .false.
        marker_count = 0
        
        ! Count valid marker characters
        do i = 1, len_trim(style_str)
            char = style_str(i:i)
            if (is_marker_char(char)) then
                marker_count = marker_count + 1
            end if
        end do
        
        ! Valid marker syntax: exactly one marker, possibly with linestyle
        if (marker_count == 1) then
            has_marker = .true.
        end if
    end function

end module