Премахнете странния невалиден знак в ruby

Имам малко XML съдържание (UTF-8), което съдържа невалидни знаци (nokogiri ми казва Line 2190, SyntaxError: PCDATA invalid Char value 15, когато се опитвам да анализирам съдържанието с Nokogiri::XML(content)).

Символът се показва в редактора на Sublime Text като "SI":

Невалиден знак

Когато се опитам да копирам героя, нищо не се копира, така че дори не мога да го потърся. Когато го отворя например в моя Atom Editor, "SI" не се показва. Въпреки това, когато преминавам през знаците с десния клавиш, трябва да пиша два пъти, за да премина през мястото, където е поставен знакът "SI".

Първо, какъв характер е това? И второ: Има ли начин в Ruby да се премахнат такива знаци. Опитах го с content.chars.select{|i| i.valid_encoding?}.join, но не премахва знака.

Актуализация

Намерих героя, като прочетох оригиналния файл с ruby. Знакът е \u000F и "\u000F".ord връща кода на знака 15. Относно http://www.fileformat.info/info/unicode/char/000f/index.htm това е SHIFT IN символ. Има ли други такива герои? Бих могъл да ги премахна с помощта на str.split("\u000F").join, но ако има други знаци като този, това не изглежда като добър подход. Някакви идеи?


person 23tux    schedule 23.02.2015    source източник
comment
Ако получавате XML от източник, който можете да повлияете или контролирате, тогава най-доброто решение е символът да бъде правилно екраниран (или премахнат, ако изобщо не трябва да е там) при източника - ако е необходимо, като подадете доклад за грешка. Анализаторът се оплаква точно. Опитът да се отгатне кои други неща не са наред в XML и предварителната обработка на XML, докато бъде добре оформен, вероятно е възможен, но ще бъде грозен и ще се прилага само към този единствен източник.   -  person Neil Slater    schedule 23.02.2015
comment
За съжаление не мога да контролирам източника. Добре, може би просто ще изчакам, докато се появи следващата грешка, ще добавя този знак към списъка и ще изчакам отново ;)   -  person 23tux    schedule 23.02.2015
comment
Един от вашите проблеми за общо решение ще бъде, че такива лоши символи понякога ще бъдат ОК в CDATA блокове. Ако е в данните по причина (разделител на запис), тогава може да предпочетете да го замените с правилно екранирана версия.   -  person Neil Slater    schedule 23.02.2015
comment
Хммм, прав си. Можете ли да дадете пример за толкова лоши герои, когато те са ок?   -  person 23tux    schedule 23.02.2015


Отговори (3)


Ако последователностите от байтове са действително невалидни за кодирането (UTF-8), тогава в ruby ​​2.1+ можете да използвате метода String#scrub. По подразбиране той ще замени невалидните знаци с „знака за заместване на unicode“ (обикновено представен като въпросителен знак в поле), но можете също да го използвате, за да ги премахнете изцяло.

Въпреки това, както отбелязвате, вашият „странен байт“ всъщност е валиден UTF-8, представляващ кодовата точка на Unicode „“, контролния знак SHIFT IN. (Добра работа при намирането на действителните включени байтове/символи, това е трудната част!)

Така че трябва да сме наясно какво имаме предвид под „такива герои“, ако искаме да ги премахнем. Герои като какво?

Nokogiri се оплаква, че е невалиден в XML област "PCDATA" (анализирани данни за знаци). Защо би бил легален unicode/UTF-8, но невалиден в XML PCDATA? Какво е законно в XML символните данни? Опитах се да го разбера, но става объркващо с спец. очевидно казва, че някои герои са „обезсърчени“ (какво?), и прави това, което според мен са противоречиви твърдения за други неща.

Не съм сигурен точно какви символи Nokogiri ще забрани от PCData, ще трябва да погледнем източника на Nokogiri (или по-вероятно източника на libxml) или да се опитаме да зададем въпрос на някой, който знае повече за източника на nokogiri/libxml.

„“ обаче е „контролен знак“, малко вероятно е да искате контролни знаци във вашите XML данни за символи (освен ако не знаете, че го правите), а спецификацията на XML изглежда обезсърчава контролните знаци (и очевидно Nokogiri/libxml всъщност ги забранява ?). Така че един от начините за тълкуване на "знаци като този" е "контролни знаци".

Можете да премахнете всички контролни знаци от низ с този регулярен израз, например:

"Some string \u000F more".gsub(/[\u0001-\u001A]/ , '') # remove control chars, unicode codepoints from 0001 to 001A
   # => "Some string  more"

Ако интерпретираме „символи като този“ като всеки знак, който не се отпечатва -- по-широка категория от „контролни знаци“ и ще включва някои, с които nokogiri изобщо няма проблем. Можем да опитаме да премахнем малко повече от просто контролни знаци, като използваме поддръжката на ruby ​​за класове символи на unicode в регулярните изрази:

some_string.gsub(/[^[:print:]]/ , '')

[:print] е документиран доста неясно като "изключва контролни знаци и подобни", така че това донякъде съвпада с нашата неясна спецификация на това, което искаме да правим. :)

Така че наистина зависи какво имаме предвид под "герои като този". Наистина, „знаци като този“ за вашия случай вероятно означава „всеки символ, който Nokogiri/libxml ще откаже да разреши“, и се страхувам, че всъщност не съм отговорил на този въпрос, защото съм не съм сигурен и не можах лесно да го разбера. Но в много случаи премахването на контролни символи или още по-добре премахването на символи, които не съвпадат с [:print], вероятно ще се справи добре, освен ако нямате причина да искате контролни символи и подобни да останат (ако сте знаели, че имате нужда от тях като разделители на записи, например).

Ако вместо да премахнете, сте искали да ги замените с unicode replacement char, който обикновено се използва за заместване на „байтова последователност, с която не можахме да се справим“:

"Shift in: \u000F".gsub(/[^[:print:]]/, "\uFFFD")
   # => "Shift in: �"

Ако вместо да ги премахнете, искате да ги избегнете по някакъв начин, те могат да бъдат реконструирани след анализ на XML.... попитайте отново с това и аз ще го разбера, но все още не съм го направил. :)

Добре дошли в справянето с проблеми с кодирането на знаци, понякога наистина става объркващо.

person jrochkind    schedule 23.02.2015
comment
Според вашата връзка законовите диапазони на знаци за XML са #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] - това ясно изключва #xF, така че не е проблем с Nokogiri или относно намирането кои знаци приема Nokogiri, както е посочено в останалата част от отговора ви. XML във въпроса на OP просто не е добре оформен. - person Neil Slater; 24.02.2015
comment
Не съм сигурен, че прочетох спецификацията по този начин, но във всеки случай е достатъчно лесно да премахнете тези невалидни знаци от изходен XML документ с gsub. Да, XML е деформиран, разбира се. - person jrochkind; 24.02.2015

Метод за премахване на контролни знаци, но НЕ празни интервали, в UTF-8 текст. Iconv първо ще преобразува низа в UTF-8 кодиране. Редът за кодиране ви позволява да посочите как да третирате невалидни знаци, но не премахва контролните знаци. Gsub се грижи за премахването на контролните знаци, но оставя празно пространство. Заместване, ако „НЕ ( НЕ контрола ИЛИ е празно пространство)“ се използва вместо заместител, ако (е контрола и НЕ е празно пространство) поради ограничения на регулярни изрази. Това работи в ruby ​​1.9.x forward, няма да работи в 1.8.7 REE.

require 'iconv'

def only_valid_chars(text)
  return "" unless text
  text = Iconv.conv('UTF-8//IGNORE', 'UTF-8', text)
  text.encode('UTF-8', 'UTF-8', {:invalid => :replace, :undef => :replace, :replace => ""})
  #remove control characters, keep white space and line endings
  text = text.gsub(/[^ [^[:cntrl:]] | [\s] ]/,'')
  return text
end
#text = "08-10-06 –"
#text = "08-10-06 â\u0080\u0093 Appr \n \r  \r\n ABC"
#only_valid_chars(text)
person Dennis Bulgatz    schedule 16.04.2015
comment
iconv беше премахнат от ruby ​​2.0+ - person Joe Van Dyk; 07.01.2016
comment
също така, че регулярният израз извежда предупреждение за символен клас с дублиран диапазон. - person Joe Van Dyk; 07.01.2016

Същото нещо се случи и с мен, докато четях имейли от xlsx файл с Roo gem.

Никога не знаех точно кои байтове/символи минават през моя низ, но тъй като знаех кои знаци бих приел, просто премахнах тези, които не съвпадат, като това:

email_chars = 'a-z0-9\.\-_@'
clean_email = email.gsub(/[^#{email_chars}]/, '')
person Mike Blake    schedule 28.11.2017