Класс Ruby MatchData повторяет захваты, а не включает дополнительные захваты, как «должно» - PullRequest
1 голос
/ 09 февраля 2010

Ruby 1.9.1, OSX 10.5.8

Я пытаюсь написать простое приложение, которое анализирует кучу файлов шаблонов HTML на основе Java, чтобы заменить точку (.) Подчеркиванием, если оно содержится в определенном теге. Я все время использую ruby ​​для этих типов служебных приложений, и думал, что было бы не проблема собрать что-нибудь с помощью поддержки регулярных выражений ruby. Итак, я создаю объект Regexp.new ..., открываю файл, читаю его построчно, затем сопоставляю каждую строку с шаблоном. Если я получаю совпадение, я создаю новую строку, используя replaceString = currentMatch.gsub ( /./, '_'), затем создайте еще одну замену как целую строку с помощью newReplaceRegex = Regexp.escape (currentMatch) и, наконец, замените обратно в текущую строку с помощью line.gsub (newReplaceRegex, replaceString). ...

Проблема, с которой я столкнулся, заключается в том, что при доступе к индексам в возвращенном объекте MatchData я получаю первый результат дважды, и в нем отсутствует вторая подстрока, которую в противном случае она должна находить. Более странно то, что при тестировании того же шаблона и того же тестового текста с использованием rubular.com, он работает, как ожидалось. Смотрите результаты здесь

Мой рисунок:

(<(?: WEBOBJECT | webobject) (?: NAME | name) = (?: [A-zA-Z0-9] +.) + (?: [A-zA-Z0-9] +) (?>))

Текстовый текст:

<WEBOBJECT NAME=admin.normalMode.someOtherPatternWeDontWant.moreThatWeDontWant>moreNonMatchingText<WEBOBJECT NAME=admin.SecondLineMatch>AndEvenMoreNonMatchingText

Вот соответствующий код:

tagRegex = Regexp.new('(<(?:WEBOBJECT|webobject) (?:NAME|name)=(?:[a-zA-Z0-9]+\.)+(?:[a-zA-Z0-9]+)(?:>))+')  

testFile = File.open ('RegexTestingCompFix.txt', "r +")
LineCount = 0
testFile.each {| htmlLine |
lineCount + = 1
ставит («Текущая строка: # {htmlLine} в номер строки: # {lineCount}»)
tagMatch = tagRegex.match (htmlLine)
if (tagMatch)

  matchesArray = tagMatch.to_a  
  firstMatch = matchesArray[0]  
  secondMatch = matchesArray[1]  
  puts "First match: #{firstMatch} and second match #{secondMatch}"  
  tagMatch.captures.each {|lineMatchCapture|  
    puts "Current capture for tagMatches: #{lineMatchCapture} of total match count #{matchesArray.size}"  
    #create a new regex using the match results; make sure to use auto escape method  
    originalPatternString = Regexp.escape(lineMatchCapture)  
    replacementRegex = Regexp.new(originalPatternString)  
    #replace any periods with underscores in a copy of lineMatchCapture  
    periodToUnderscoreCorrection = lineMatchCapture.gsub(/\./, '_')  
    #replace original match with underscore replaced copy within line  
    htmlLine.gsub!(replacementRegex, periodToUnderscoreCorrection)  
    puts "The modified htmlLine is now: #{htmlLine}"    
    }  
end  

}

Я бы подумал, что должен получить первый тег в matchData [0], а затем второй тег в matchData 1 , или, что я на самом деле делаю, потому что я не знаю, сколько совпадений я попадем в любую данную строку - matchData.to_a.each. И в этом случае matchData имеет два захвата, но они оба являются первым тегом match

which is: <WEBOBJECT NAME=admin.normalMode.someOtherPatternWeDontWant.moreThatWeDontWant>

Итак, какого чёрта я делаю неправильно, почему рубулярный тест дает мне ожидаемые результаты?

Ответы [ 3 ]

1 голос
/ 10 февраля 2010

Я закончил тем, что использовал подход String.scan, единственная сложная задача заключалась в том, чтобы выяснить, что это возвращает массив массивов, а не объект MatchData, поэтому с моей стороны было некоторое первоначальное замешательство, главным образом из-за моего рубинового зеленого Конечно, но он работает, как и ожидалось. Кроме того, я обрезал регулярное выражение в соответствии с предложением Тревока. Но случай змеи? Никогда ... ;-) Во всяком случае, здесь идет:

tagRegex = /(<(?:webobject) (?:name)=(?:\w+\.)+(?:\w+)(?:>))/i  
testFile = File.open('RegexTestingCompFix.txt', "r+")  
lineCount=0  
testFile.each do |htmlLine|  
  lineCount += 1  
  puts ("Current line: #{htmlLine} at line num: #{lineCount}")  
    oldMatches = htmlLine.scan(tagRegex) #oldMatches thusly named due to not explicitly using Regexp or MatchData, as in "the old way..."  
    if(oldMatches.size > 0) 
      oldMatches.each_index do |index|   
        arrayMatch = oldMatches[index]  
        aMatch = arrayMatch[0]  
        #create a new regex using the match results; make sure to use auto escape method  
        replacementRegex = Regexp.new(Regexp.escape(aMatch))  
        #replace any periods with underscores in a copy of lineMatchCapture  
        periodToUnderscoreCorrection = aMatch.gsub(/\./, '_')  
        #replace original match with underscore replaced copy within line, matching against the new escaped literal regex  
        htmlLine.gsub!(replacementRegex, periodToUnderscoreCorrection)  
        puts "The modified htmlLine is now: #{htmlLine}"         
      end # I kind of still prefer the brackets...;-)  
    end  
  end

Теперь, почему MatchData работает так, как работает? Похоже, что его поведение на самом деле является ошибкой и, конечно, не очень полезно в общем, если вы не можете его получить, предоставьте простой способ доступа ко всем совпадениям. Просто мои $ .02

1 голос
/ 09 февраля 2010

Вы хотите использовать String#scan вместо Regexp#match:

tag_regex = /<(?:WEBOBJECT|webobject) (?:NAME|name)=(?:[a-zA-Z0-9]+\.)+(?:[a-zA-Z0-9]+)(?:>)/

lines = "<WEBOBJECT NAME=admin.normalMode.someOtherPatternWeDontWant.moreThatWeDontWant>moreNonMatchingText\
     <WEBOBJECT NAME=admin.SecondLineMatch>AndEvenMoreNonMatchingText"

lines.scan(tag_regex)
# => ["<WEBOBJECT NAME=admin.normalMode.someOtherPatternWeDontWant.moreThatWeDontWant>", "<WEBOBJECT NAME=admin.SecondLineMatch>"]

Несколько рекомендаций по следующим рубиновым вопросам:

  • переводы строк и пробелов - ваши друзья, вы не теряете баллы за использование большего количества строк в вашем коде;
  • использование do-end на блоках вместо {}, значительно улучшает читаемость
  • объявление переменных в случае змеи (hello_world) вместо случая верблюда (helloWorld)

Надеюсь, это поможет

0 голосов
/ 09 февраля 2010

Маленькие биты: Это регулярное выражение помогает вам получить "normalMode" .. Но не "secondLineMatch":

<webobject name=\w+\.((?:\w+)).+> (with option 'i', for "case insensitive")

Это регулярное выражение помогает вам получить "secondLineMatch" ... Но не "normalMode":

<webobject name=\w+\.((?:\w+))> (with option 'i', for "case insensitive").

Я не очень хорош в регулярных выражениях, но буду продолжать трудиться над этим ..:)

И я не знаю, поможет ли это вам вообще, но вот способ получить оба:

<webobject name=admin.(\w+) (with option 'i').
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...