Category: it

Category was added automatically. Read all entries about "it".

Torbasow

Парсинг в PHP многомерного массива PostgreSQL

Суть проблемы в том, что база данных возвращает все поля как текст. В том числе и массивы,— упаковывая их отнюдь не как удобоваримый JSON, а в некотором своём формате. А нам этот формат не нужен, нам нужен массив. Поэтому в PHP ответ базы приходится распаковывать.

Не совсем в PHP. Я почитал сложным и ненадёжным писать парсер в PHP самому, а готового решения, вроде бы, нет. Поэтому обращаюсь из PHP к возможностям самого PostgreSQL.

Раньше у нас были только одномерные массивы, и для их парсинга было написано такое решение:

$fieldValueArr=str_getcsv(trim($fieldValue, '{}'), ',', '"', '\\');// http://stackoverflow.com/questions/3068683/convert-postgresql-array-to-php-array
//Массивы обычно имеют разделителем запятую, кроме box[], который имеет разделителем точку с запятой, но разделитель может быть переопределён настройкой typdelim для типа
foreach($fieldValueArr as &$fieldValueArrEl){
	//str_getcsv имеет в PHP 5.3.6 баг (https://bugs.php.net/bug.php?id=55413), поэтому нужна дополнительная постобработка для разэкранирования
	$fieldValueArrEl=preg_replace('/\\\\([\\"])/', '$1', $fieldValueArrEl);
	$fieldValueArrEl=$fieldValueArrEl==='NULL'?NULL:$this->parseResultField($fieldValueArrEl, $fieldType);
}

Но упаковка многомерного массива за CSV в дополнительных скобочках уже не сойдёт. Нужно изощряться. Как-то так:

function parseResultFieldArray($fieldValue, $fieldType){
	$escapedFieldValue= pg_escape_string($fieldValue);//в PHP 5.4.4+ лучше использовать pg_escape_literal
	//$fieldType для пущей надёжности можно пропускать через pg_escape_identifier, но только в PHP 5.4.4+
	$resultResource=$this->simpleQuery("WITH arr(val) AS(
		SELECT '$escapedFieldValue'::{$fieldType}[]
	),
	vals(val) AS(
		SELECT unnest(val) FROM arr
	),
	dims(val) AS(
		SELECT generate_series(1, array_ndims(arr.val)) FROM arr
	)
	SELECT
		(SELECT array_agg(vals.val) FROM vals),
		(SELECT string_agg(array_length(arr.val, dims.val)::text, ',') FROM arr CROSS JOIN dims)");
	list($fieldValue, $fieldArrayDims)=pg_fetch_array($resultResource);
	$fieldValueArr=str_getcsv(trim($fieldValue, '{}'), ',', '"', '\\');// http://stackoverflow.com/questions/3068683/convert-postgresql-array-to-php-array
	//Массивы обычно имеют разделителем запятую, кроме box[], который имеет разделителем точку с запятой, но разделитель может быть переопределён настройкой typdelim для типа
	foreach($fieldValueArr as &$fieldValueArrEl){
		//str_getcsv имеет в PHP 5.3.6 баг (https://bugs.php.net/bug.php?id=55413), поэтому нужна дополнительная постобработка для разэкранирования
		$fieldValueArrEl=preg_replace('/\\\\([\\"])/', '$1', $fieldValueArrEl);
		$fieldValueArrEl=$fieldValueArrEl==='NULL'?NULL:$this->parseResultField($fieldValueArrEl, $fieldType);
	}
	$fieldArrayDims=explode(',', $fieldArrayDims);
	array_shift($fieldArrayDims);
	$fieldArrayDims=array_reverse($fieldArrayDims);
	foreach($fieldArrayDims as $fieldArrayDim){
		$fieldValueArr=array_chunk($fieldValueArr, $fieldArrayDim);
	}
	return $fieldValueArr;
}

Попросту говоря, мы берём упаковку многомерного массива и суём её снова в запрос к базе, а вытаскиваем оным запросом тот же массив, но развёрнутый в одномерный (который мы уже знаем, как распаковать), плюс информацию о его измерениях, с помощью которой сворачиваем одномерный массив в многомерный.

Конечно, с помощью array_to_json вышло бы гораздо, гораздо проще. Но, увы, эта функция появилась в PostgreSQL 9.2, а мы только-только смогли перейти с версии 8.4 на 9.1.6.

Torbasow

Сортировка в агрегирующей функции в PostgreSQL

В PostgreSQL до 9-й версии не было сортировки внутри агрегирующей функции. То есть нельзя было написать как-нибудь вроде SELECT array_agg(price ORDER BY year)…

А как быть, если очень хочется и, в общем-то, нужно? А версия 8.4, и повысить её ну просто никак нельзя, ультимативное ограничение проекта.

Мэтью Вуд посоветовал использовать упорядоченный подзапрос. Да, собственно, официальный мануал так и говорит: «Обеспечение входных значений отсортированным подзапросом, однако, обычно будет работать». Умилительно написал об этом Том Лейн, один из главных разработчиков этой СУБД: «Ну, спека SQL этого не требует (на самом деле, я думаю, выражение ORDER BY внутри подзапроса даже вообще не правомерно по спеке SQL)… но мы обещаем это в текущей реализации, и сомневаюсь, что мы нарушим это обещание в будущем, ведь это мощно настраиваемое поведение для пользовательских агрегатов».

Но тут обнаружилась ещё одна засада. Если в главном запросе нужна группировка (GROUP BY), сортировка из подзапроса внезапно разрушается!

И тут уж я думал, придётся отказаться от агрегации, и разработчице, которой я этот метод тоже присоветовал, придётся сказать, что она потратила полдня зря из-за того, что я её направил на ложный путь. Ах-ах, потеря лица!

Но, хвала богам, благородный Лейн оговорился и на этот счёт: «Заметьте, что если вам нужно использовать выражение GROUP во внешнем запросе, лучше всего будет отсортировать выдачу внутреннего запроса сначала по полям группировки внешнего запроса». Лейн поясняет, отвечая на решение Михаэля Фура, участника дискуссии: «Этот способ будет работать и если планировщик решит использовать GroupAggregate (а он, вероятно, так и сделает, если увидит, что может избежать ещё одного шага сортировки). Способ же, указанный Михаэлем, будет работать только если план будет использовать HashAggregate,— но если планировщик решит, что во внешнем запросе нужно использовать Sort+GroupAggregate, пересортировка вероятно разрушит требуемый порядок…»

Torbasow

Переиспользование XSLT-шаблона

…Мне потребовалось переопределить шаблон, определённый в базовом XSLT-файле комплекса, импортируемом (xsl:import) в XSLT-файл конкретного модуля. До сих пор всё шло гладко, я просто определял именованный шаблон с тем же именем и он перекрывал базовый. Но тут возникли две особенности: во-первых, хотелось задействовать базовый шаблон, сослаться на него — не копипастить же; во-вторых, исправление (дополнительный кусок кода) следовало не дописать, а внести внутрь возвращаемого базовым шаблоном кода. Обе особенности потребовали некоторого изыска.

Для начала, оказалось, что использовать шаблон из импортированного файла можно,— но, внезапно, не именованный. И как будто даже в XSLT 2 такой возможности не появилось. Пришлось воспользоваться советом Джени Теннисон: заменить вид (как базового, так и пронаследованного) шаблона с <xsl:template name="имя"> на <xsl:template match="node()" mode="имя">, а его вызов <xsl:call-template name="имя"/> на <xsl:apply-templates select="." mode="имя"/>, после чего xsl:apply-imports работает желаемым образом, вызывая базовый шаблон из пронаследованного.

Далее, выдачу базового шаблона следовало раскурочить и собрать заново, вставив новый кусок кода. Пришлось воспользоваться переменной и функцией из расширения EXSLT - Common (ведь дело происходит в XSLT1:

<xsl:template match="node()" mode="имя">
	<xsl:variable name="переменная">
		<xsl:apply-imports/>
	</xsl:variable>
	<xsl:for-each select="exsl:node-set($переменная)/*[1]">
		<xsl:element name="{name()}">
			<xsl:copy-of select="@*|node()"/>
			<!-- Добавленный код -->
		</xsl:element>
	</xsl:for-each>
</xsl:template>

…Но что если нам нужно не просто добавить код, а влезть внутрь переопределяемого шаблона и изменить что-то в его недрах? Оказалось, так тоже можно:

<xsl:template match="node()" mode="имя">
	<xsl:variable name="переменная">
		<xsl:apply-imports/>
	</xsl:variable>
	<xsl:for-each select="exsl:node-set($переменная)/*">
	<xsl:element name="{name()}">
			<xsl:apply-templates mode="имя2"/>
		</xsl:element>
	</xsl:for-each>
</xsl:template>

<xsl:template match="@*|node()" mode="имя2">
	<xsl:copy-of select="."/>
</xsl:template>

<xsl:template match="паттерн" mode="имя2">
	<xsl:element name="{name()}">
		<xsl:copy-of select="@*|node()"/>
		<!-- Добавленный код -->
	</xsl:element>
</xsl:template>
Torbasow

Юникодовские имена переменных в Javascript

Я проникся слишком большим энтузиазмом, обнаружив, что в именах переменных Javascript можно использовать символы Юникода. Кириллица, греческий алфавит — всё проходит на ура, хотя [редактор] Komodo Edit ругается, подчёркивает красным. А попытался определить [в процедуре экстраполяции методом наименьших квадратов] переменную Σx² для наглядности — и обломался. Оказывается, не все символы Юникода.

А вот какие. Начинаться идентификатор может с символа в категориях Юникода «Прописная буква (Lu)», «Строчная буква (Ll)», «Заглавная буква (Lt)», «Буква-модификатор (Lm)», «Буква, прочие (Lo)» или «Буква-число (Nl)», а также доллар ($) или знак подчёркивания (_). (Последний, кстати, начлаб, да и не только он, регулярно называет «нижнее подчёркивание», что каждый раз погружает меня в недоумение!) Дальше могут встречаться также символы из категорий «Непробельный знак (Mn)» или «Комбинирующий пробельный знак (Mc)», «Десятичное число (Nd)», «Соединяющая пунктуация (Pc)».

Греческая сигма — это категория Lu, нормально, а вот SUPERSCRIPT TWO — категория No («Числа, прочие» / Number, Other). Упс.

…Вот тут, кстати, пример с написанием через числовой код: var \u1000={\u1001: "foo", \u1011: "bar"}. Здесь использованы бирманские буквы (фактически, здесь: var က={ခ: "foo", ထ: "bar"}, если у вас есть подходящий шрифт, вроде Code2000), это категория Lo.

UPD: в PHP свободы наименования куда меньше — это латинские буквы, знак подчёркивания, символы диапазона от \x7f до \xff  и, если только не в начале, арабские цифры. Если подчёркивание использовать как разделитель уже почему-то нельзя, остаётся выбрать что-то из пунктуации и символов Latin-1. Мне тут вроде подошёл по смыслу §, но их большой недостаток в том, что с обычной клавиатуры их просто так не наберёшь.

Torbasow

Перевод даты в строку (Javascript)

В коде нашего сотрудника узрел следующее:

var dateString = (date.getDate() < 10 ? '0' + date.getDate() : date.getDate()) + '.' + (date.getMonth() < 9 ? '0' + (date.getMonth() + 1) : date.getMonth()+1) + '.' + date.getFullYear();

Задумался. Ну, то, что в нашей библиотеке давно реализована функция lpad, и то, что такое выражение тоже надо выносить в библиотеку, рядышком с formatNumber, это всё само собой. Но неужто нет лаконичного способа?

На самом деле, есть даже два (очевидная дата.toLocaleDateString() возвращает нечто неприемлемое, типа «9 Март 2013 г.»): дата.toLocaleFormat("%d.%m.%Y") и дата.toISOString().substring(0, 10).

Метод toLocaleFormat внедрён в Javascript 1.6 (Fx1.5), но он нестандартный и есть предложение его выпилить.

С другой стороны, есть метод toISOString, внедрённый в Javascript 1.8 (Fx3) и включённый в стандарт ECMA-262 5-й редакции. Он, однако же, выдаст нам фиксированный формат даты (ISO 8601), хорошо подходящий для сортировки, но отличающийся от привычного русского.

Оба варианта не очень хороши, но на будущее есть стандарт по интернационализации, хотя пока и не реализованный [в «Файерфокс»]. Ну, что, надо полифилл писать…

Torbasow

Ширина блока по содержимому в Firefox

Файерфокс-тянУра, есть способ задать блоку ширину по содержимому без побочных эффектов. До сих пор мне было известно три общих способа:

  • position: absolute,

  • float или

  • display: table-cell либо display: table

и один для «Файерфокс» (пора уже перестать обманывать публику, и признать, что наша тян — женского рода), из внушающего надежды, но недовнедрённого (на самом деле, у других браузеров есть какие-то аналоги) и недавно кардинально переделанного модуля Flexible Box Layout, — display: -moz-box.

Но первые два влияют на размещение блока, а вторые два ограничивают использование position: relative (хотя в новом, 17-м, «Файерфоксе» удалось примирить display: table; position: relative;, но это новый, а разработка у нас идёт под Fx3.6).

Но вот оказалось, что с Fx3 есть значение свойства width:-moz-fit-content (и некоторые ещё — реализация и стандарт).

Torbasow

Арифметика скандальным образом применяется к null

Какой кошмар. В Javascript null/1e6= …нет, не null и не NaN (Not a Number — не число), и ошибку не выбрасывает. Это, видите ли, 0! Обычный ноль, который дальше спокойно участвует в вычислениях.

Увы и ах, и Javascript и PHP как ни в чём ни бывало производят с null’ом арифметические операции, втихомолку конвертируя его в ноль.

Это неожиданное поведение. По аналогии с PostgreSQL я рассчитывал на совсем иное. Там из null’а уже никакого числа не получишь (если только не в аггрегирующей функции, что меня изрядно смущает).

Я так понимаю, когда-то такое поведение заложили в угоду быдлокодерам, которые не хотят, чтобы их скрипты ломались ни при каких обстоятельствах. «Перехват исключений? Не, не слышал…» Да так и оставили. Вообще арифметика в Javascript может много весёлых минут доставить. Тут и 0.2+0.1!==0.3, и на ноль можно делить, и есть разные нули, положительный и отрицательный, которые при проверке тожественны, но при делении на них или при попадании в тригонометрию дают разные результаты. Прикиньте, функция от двух тождественных аргументов даёт разные результаты!

UPD: Поэтому в Javascript при прочих равных лучше, наверное, использовать undefined, ибо он при попытке с ним что-то вычислять честно выдаст NaN, и ошибку будет легко обнаружить и локализовать.

UPD-2: Вот тут человек догадался заглянуть в спецификацию, и обнаружил, что:

The difference is, null is used when there should be a reference to an object. Undefined has much broader use.

ECMA-262 states “The undefined value is […] used when a variable has not been assigned a value”, while “The null value […] represents the null, empty or non-existent reference.” That means, null is used when you expect the reference to an object, while undefined is used when you have not yet defined a value. By the way, this is why typeof null is “object”.

Коротко говоря, null должен использоваться там, где предполагается ссылка на объект, но объекта по каким-то причинам не получилось,— соответственно, в арифметике его вообще не должно появляться. Это отлично от положения дел в базах данных. Должно кидаться исключение, по хорошему, да.

UPD-3 (2013-11-25): Однако обнаружился случай, когда null может появляться вместо undefined. Выведите JSON.parse(JSON.stringify([undefined])); вы получите [null], ага. В JSON нет undefined. А между прочим, данные с сервера приходят обычно именно в этом виде. Значит, распаковывать их правильнее так:

JSON.parse(JSON_строка, function(key, value) value===null?undefined:value)
Torbasow

Сложить два объекта (Javascript)

В Javascript’е плохо с операциями над объектами. И разрабатываемый сейчас новый стандарт, кажется, не предлагает компактного синтаксиса.

Чтобы просто сложить два объекта, приходится вместо очевидного объект1 + объект2 или хотя бы Object.merge(объект1, объект2) прибегать к такой конструкции:

Object.extend({}, объект1, объект2)

Причём, если метод create доступен с Fx4, метод extend только предлагается в ECMAScript 6, а пока эмулируется:

if(!Object.extend)
	Object.extend=function(destination){
		Array.prototype.slice.call(arguments, 1).forEach(function(source){
			Object.getOwnPropertyNames(source).forEach(function(name){
				destination[name]=source[name];
			});
		});
		return destination;
	};

(Для ECMAScript 6, кстати, предложен компактный синтаксис.)

А попробуйте-ка сравнить два объекта. Если один создан из другого (объект2=Object.create(объект1)), то они всегда не равны (объект2!==объект1), даже если мы в нём ничего не поменяли! Мне пока не удалось найти хорошего способа — хотя множество громоздких или ненадёжных решений существует.

Torbasow

Как я в CKEditor неразрывный пробел вставлял

Да, я считаю, правильная типографика — это важно. Предыдущая фраза набрана через неразрывный пробел и настоящее длинное тире, а не заморское короткое или какой-нибудь дефисоминус. Для таких вещей у меня расширение Character Palette от благородного кантонца Райана Ли. Судя по статистике использования, таких, кроме меня, на всём свете шестьсот восемьдесят человек. Так что для Маоизм.ру я решил делать интегрированное решение.

И отчасти обломался. WYSIWYG-редактор CKEditor, позиционирующийся как один из лучших бесплатных, оказался тяжёл в отладке и забагован. Для начала я обнаружил, что он по умолчанию конвертит всё, что может, в HTML-сущности, то есть мнемонические записи вида &nbsp;. По стандарту XML так следует поступать только с угловыми скобками (точнее, тем, что в обиходе ошибочно называется угловыми скобками), амперсандом и машинописными кавычками. Остальное — зачем? У нас же Юникод. …Отключение конвертации оказалось задачей нетривиальной, просто выставление нужных настроек приводит к тому, что конвертиться, причём неправильно, начинают самые безобидные буквы:

CKEDITOR.config.basicEntities=false;
CKEDITOR.config.entities_additional='gt,lt,amp,apos,quot';

Полез исправлять, так ведь рабочий код этого редактора пожат, неудобно. Ладно, нашёл нужное место, исправил (описание бага и патчи уже потом нашёл).

И вот тут я столкнулся с невозможностью вставить неразрывный пробел в «Файерфоксе» (уже написав для этого плагин кнопки). Это баг, известный уже четыре года. Его уже обходили в FCKEditor, предшественнике CKEditor, но в новом продукте эта проблема вновь всплыла. Я попытался приспособить старый патч, но безрезультатно. В общем, пришлось пока отложить этот вопрос.

Torbasow

Латинический шовинизм в модуле freelinking для Drupal

Моему возмущению нет предела.

Заканчиваю (в основном, на работе в перерыве) подключение запланированных модулей к Маоизм.ру. Последние несколько дней провозился с организацией Wiki; думал, потом подключу плагины к WYSIWYG-редактору, и можно будет переходить к заполнению таксономии и тонкой настройке. Ан нет, упёрся в тупик на финальной стадии: фрилинкинг не работает и всё тут. Речь об упрощённой организации ссылок: ну, типа, когда вы в Википедии набираете что-то вроде [[Российская маоистская партия]], движок автоматически подставляет ссылку на соответствующую статью. А тут ни черта не подставляет. Вот что бы стал делать простой смертный? Погуглил бы. Погуглил. Не нашёл ничего.

Сегодня занялся этим дома, где у меня открыт доступ к PHP-коду движка. Несколько диагностических сообщений — и вот она, истина. Ларчик просто открывался: /(?<!\\)\[\[([A-Za-z0-9]{1}.+\:?.+)]]/Uu в модуле freelinking. Чувствуете подвох? Да, квантификатор ? относится тут только к двоеточию, а не к названию плагина вместе с двоеточием, как должно быть в соответствии с описанием модуля. То есть вот это будет работать — [[Path:Российская маоистская партия]]. И у западных пользователей работает без названия плагина, как полагается,— пока то, что они пишут в скобках, начинается с буквы из английской латиницы (или цифры). Регулярка принимает это выражение, как будто оно начинается с корректного указания плагина, дальнейший код не видит в совпадающем фрагменте двоеточия, заключает, что плагин не указан, подставляет умолчательный и всё тип-топ. …Пока какой-нибудь перфекционист, мой брат по духу, не вздумает написать, например, [[Škoda Holding]]. Тогда его ждёт сюрприз — фрилинкинг не сработает, ссылка не подставится.

Оказывается, эта проблема обсуждалась с участием кириллицепишущего и шведа. В результате родился патч, в коем регулярка заменяется на /(?<!\\)\[\[([A-Za-z0-9]{1}.+\:?.+|.+)]]/Uu. Сделайте меня развидеть это! Она работает, но, вероятно, пропускает и множество некорректных вариантов. Не разумею, как можно сразу не углядеть, что это полностью эквивалентно более простому /(?<!\\)\[\[(.+)]]/Uu, то есть чему угодно в паре квадратных скобок вообще? Я так понимаю, у патчеров в мозгах витала нездоровая мысль, что пайп (|) в регулярке эквивалентен возможному пайпу в синтаксисе фрилинкинга.

Придётся зарегистрироваться, чтобы рассказать им, как они неправы. Мне видится, что правильно было бы так: /(?<!\\)\[\[(([A-Za-z0-9]{1}.+\:)?.+)]]/Uu. Испытано, работает.