Think.JS №13

  • Для начинающих: Поиск с параметрами. Часть I
  • Полевые испытания: Проблема внутреннего содержания
  • Учим матчасть: Экспорт файлов
  • Полезный скрипт: Пропал кусок текста: «зовут дядя Федор»

Содержание

Для начинающих: Поиск с параметрами. Часть I

Полевые испытания: Проблема внутреннего содержания

Учим матчасть: Экспорт файлов

Полезный скрипт: Пропал кусок текста: «зовут дядя Федор»

Для начинающих

Поиск с параметрами. Часть I

Мы уже рассматривали механизмы поиска текста в InDesign в общем виде. Теперь закрепим знания, решив практическую задачу по обработке текста при помощи «умной» замены.

Наглядным примером необходимости такой замены является телепрограмма. Разделителем между часами и минутами во времени начала передачи бывает либо точка, либо двоеточие: «12.30» или «12:30». Как правильно – не столь важно, основная задача зачастую в том, чтобы привести текст к одному виду, в нашем случае пусть будет точка. Вручную заменять текст – долго и трудно, а штатные средства InDesign не позволят выполнить замену двоеточия между цифрами на точку без дополнительных ухищрений (подробно описано в статье …). В решении таких задач скрипты оказывают помощь, которую трудно переоценить.

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

smartFindChange.jsx
Скрипт заменяет двоеточие между цифрами на точку
with (app) {
  var myDoc = documents.add();
  var myTextFrame = myDoc.textFrames.add({visibleBounds: [10,10,60,60]});
// Создаем текстовый фрейм  
  myTextFrame.contents = "18:10 - 19:10 - профилактика. 21:10 художественный фильм.";
  var myStory = myTextFrame.parentStory;
  findPreferences = null;
  changePreferences = null;
// Обнуляем дополнительные настройки поиска и замены
  var myResult = myStory.search("^9:^9", false, false);
// Ищем текст с помощью спецзнаков: "любая цифра:любая цифра"
  for (var counter = 0; counter < myResult.length; counter++) {
// В каждом найденнном тексте выполняем замену двоеточия на точку
    myResult[counter].search(":", false, false, ".");
  }
}

Несомненным и очевидным плюсом такого решения является то, что скрипт по определению не может нарушить форматирование текста, которое уже было сделано до запуска. Минус такого решения – медленное выполнение, поскольку для каждого найденного текста выполняется отдельный поиск с заменой. При обработке достаточно объемной телепрограммы скрипт будет работать крайне медленно (при достижении критического значения найденных текстов – 10 000 и более – вообще возможно зависание).

Для убыстрения обработки текста есть другой метод, основанный на временном применении к найденным текстам специально созданного стиля знака. Замена выполняется при помощи одного вызова метода search(), что значительно убыстряет обработку, однако одновременно возникает риск нарушить ранее установленное форматирование. Как в механике: выигрываешь в расстоянии – проигрываешь во времени.

 

smartFindChangeUseStyle.jsx
Скрипт демонстрирует поиск-замену с использованием временного стиля
with (app) {
  var myDoc = documents.add();
  var myStyle = myDoc.characterStyles.add();
// Создаем новый стиль знака без настроек
  var myTextFrame = myDoc.textFrames.add({visibleBounds: [10,10,60,60]});
// Создаем текстовый фрейм  
  myTextFrame.contents = "18:10 - 19:10 - профилактика. 21:10 художественный фильм.";
  var myStory = myTextFrame.parentStory;
  findPreferences = null;
  changePreferences = null;
// Обнуляем дополнительные настройки поиска и замены
  changePreferences.appliedCharacterStyle = myStyle;
// Указываем, что при поиске найденный текст будет форматироваться созданным стилем
  myStory.search("^9:^9", false, false);
// Ищем текст с помощью спецзнаков: "любая цифра:любая цифра"
  changePreferences = null;
// Обнуляем настройки замены
  findPreferences.appliedCharacterStyle = myStyle;
// Указываем, что при следующем поиске нужно искать только тексты, к которым присвоен созданный стиль
  myStory.search(":", false, false, ".");
// Заменяем двоеточие на точку
  myStyle.remove();
// Удаляем созданный стиль
  findPreferences = null;
  changePreferences = null;
// Обнуляем дополнительные настройки поиска и замены  
}

Полевые испытания

Проблема внутреннего содержания

Содержимое (contents) текстовых фреймов бывает разным. В подавляющем большинстве случаев содержимое может быть представлено как текст в формате Unicode, в том числе и многие специальные символы, например, неразрывный пробел. Однако есть тип символов, которые не могут быть представлены как Unicode-последовательность. Как с ними правильно работать?

В первую очередь неплохо разобраться, что такое уникод (Unicode). В целом это кодировка, которая дает возможность использования почти 65 тысяч различных знаков. Это означает, что системы, поддерживающие такую кодировку, могут поддерживать различные национальные языки без перенастройки. Шрифты, поддерживающие уникод, могут использоваться для записи текстов на разных языках; один и тот же шрифт может применяться для записи английского, немецкого, русского, украинского текста и для многих других, вплоть до арабского. Причем в программах, правильно воспринимающих Уникод, эти тексты должны выглядеть одинаково. Кроме букв и разнообразных знаков препинания, в Уникоде поддерживается большое количество специальных символов, в том числе несколько видов пробельных символов.

В качестве эксперимента можно экспортировать текст из InDesign в текстовый файл в кодировке Unicode и открыть его в другой программе, поддерживающей эту кодировку, например в MS Word. За исключением некоторых знаков текст будет выглядеть идентично. Однако, если текст, экспортированный из InDesign в уникод-формате, поместить в другой документ InDesign, то полной идентичности все равно не будет. Попробуйте ради эксперимента перенести текст, содержащий символ Auto Page Number через экспорт в файл в кодировке Unicode. Ничего не получится, поскольку этот символ не является символом Уникода, а представляет группу символов «надстройки» над уникодом. Такие символы воспринимаются только в InDesign (и в InCopy – по понятным причинам).

В документации от Adobe приведен полный список таких символов с указанием их значений (десятизначное число). Все эти значения являются свойствами нумератора SpecialCharacters, который содержит 38 (справедливо для InDesign CS2, в других версиях может быть меньше или больше) свойств для обозначения различных специальных символов. Некоторые специальные символы могут быть представлены в виде уникод-начения, но производители InDesign отчего-то посчитали нужным представить их в виде свойств нумератора SpecialCharacters.

Например, специальный символ Bullet («пуля») имеет уникод-значение 2022 и значение нумератора SpecialCharacters.bulletCharacter. Значения эти можно использовать следующим образом.

unicodeBullet.jsx
Скрипт демонстрирует использование уникод-значения символа Bullet
with (app) {
  var myDoc = documents.add();
  var myTextFrame = myDoc.textFrames.add({visibleBounds: [10,10,60,60]});
// Создаем текстовый фрейм  
  myTextFrame.contents = "\u2022 Пункт первый";
// Указываем содержимое с использованием уникода
}
specialBulle.jsx
Скрипт демонстрирует использование специального значения символа Bullet
with (app) {
  var myDoc = documents.add();
  var myTextFrame = myDoc.textFrames.add({visibleBounds: [10,10,60,60]});
// Создаем текстовый фрейм  
  myTextFrame.contents = " Пункт первый";
  myTextFrame.insertionPoints.firstItem().contents = SpecialCharacters.bulletCharacter;
// Указываем содержимое первой ТОЧКИ ВСТАВКИ при помощи нумератора
}

Почему акцент сделан именно на использовании точки вставки (insertionPoint)? Потому, что это наиболее часто используемый метод. Вообще, вставлять дополнительный текст в уже существующий удобнее всего методом изменения свойства contents нужной точки вставки (потому она так и называется – точка вставки). Для замены текста можно изменять свойство contents у объектов другого типа, например, знаков (character), слов (word), абзацев (paragraph) и др.

insertionPointContents.jsx
Скрипт демонстрирует использование insertionPoint для вставки дополнительного текста в исходный
with (app) {
  var myDoc = documents.add();
  var myTextFrame = myDoc.textFrames.add({visibleBounds: [10,10,60,60]});
// Создаем текстовый фрейм  
  myTextFrame.insertionPoints.firstItem().contents = " Пункт первый";
// Указываем содержимое первой точки вставки вместо указания содержимого текстового фрейма
  myTextFrame.insertionPoints.firstItem().contents = SpecialCharacters.bulletCharacter;
// Указываем содержимое первой точки вставки при помощи нумератора
}

Заметим, что изменение свойства contents у точки вставки автоматически создает новый текст после этой точки, при этом содержимое самой точки вставки не меняется (оно по определению является пустым текстовым значением). Поэтому можно вставлять новый текст без опасения удалить или изменить ранее вставленный.

Специальные символы нумератора SpecialCharacters, в отличие от простого текста, следует вставлять только по одному. Дело в том, что значением свойства нумератора является десятизначное число, поэтому при использовании выражения вида myTextFrame.insertionPoints.firstItem().contents = SpecialCharacters.bulletCharacter + SpecialCharacters.indentHereTab происходит следующее. Десятичные значения свойств SpecialCharacters.bulletCharacter и SpecialCharacters.indentHereTab складываются, полученное значение скрипт пытается вставить в текст, что в большинстве случаев вызывает ошибку выполнения.

insertTwoSpecial.jsx
Скрипт демонстрирует вставку двух специальных символов последовательно
with (app) {
  var myDoc = documents.add();
  var myTextFrame = myDoc.textFrames.add({visibleBounds: [10,10,60,60]});
// Создаем текстовый фрейм  
  myTextFrame.contents = "Вечор, ты помнишь, вьюга злилась, на мутном небе мгла носилась, луна, как бледное стекло, сквозь тучи мрачные желтела, и ты печальная сидела. А нынче - погляди в окно!";
  myTextFrame.insertionPoints.firstItem().contents = SpecialCharacters.indentHereTab;
// Указываем содержимое первой точки вставки при помощи нумератора  
  myTextFrame.insertionPoints.firstItem().contents = SpecialCharacters.bulletCharacter;
// Указываем содержимое первой точки вставки при помощи нумератора
}

Важно запомнить, что сначала вставляется символ, который должен быть самым левым (при записи слева направо, наоборот, самым правым) во вставляемом тексте.

Кроме вставки специальных символов, не менее частой задачей является удаление таких символов, как правило, разнообразных break-символов (force line break, frame break, page break и т.п.). Как это делается?

Как уже было сказано, специальные символы бывают двух видов. Для некоторых предусмотрено уникод-значение (например, force line break). С обработкой таких символов проблем, как правило, не возникает. При поиске можно воспользоваться либо уникод-значением такого символа, либо специальным символом поиска в InDesign (можно посмотреть в диалоге поиска-замены).

specialFindChange.jsx
Скрипт демонстрирует поиск-замену с использованием специальных символов
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  try {
    var myStory = selection[0].parentStory;
  } catch (error) {
    exit();
  }
  myStory.search("^n", false, false, "^p");
// Заменяем force line break на знак конца абзаца
}

Тот же результат при использовании уникод-значений.

unicodeFindChange.jsx
Скрипт демонстрирует поиск-замену с использованием уникод-значений специальных символов
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  try {
    var myStory = selection[0].parentStory;
  } catch (error) {
    exit();
  }
  myStory.search("\u000A", false, false, "\u000D");
// Заменяем force line break на знак конца абзаца
}

Но не все специальные символы имеют уникод-значение. Некоторые являются «надстройкой» над обычным знаком. Например, column break использует уникод-значение 000D, что соответствует знаку конца абзаца. Как обработать такие символы? Для этого нужно найти все знаки, для которых специальные символы являются дополнением, и проверить их свойство contents. Если свойство содержит одно из значений нумератора специальных символов (или просто не соответствует стандартному текстовому значению), значит, найденный знак является специальным символом.

removeAllBreaks.jsx
Скрипт заменяет все break-символы, кроме force line break, на символы конца абзаца
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  try {
    var myStory = selection[0].parentStory;
  } catch (error) {
    exit();
  }
  var myResult = myStory.search("^p", false, false);
// Ищем все знаки абзаца
  for (var counter = 0; counter < myResult.length; counter++) {
    if (myResult[counter].contents != "\u000D") {
      myResult[counter].contents = "\u000D";
    }
  }
}

Учим матчасть

Экспорт файлов

Как правило, файлы InDesign в качестве конечного продукта не используются. Созданные документы либо печатаются, либо экспортируются в другой формат для дальнейшего использования.

В отличие от метода импорта файлов, который для разных объектов имеет свои нюансы, метод экспорта для всех объектов, которые можно экспортировать, определен одинаково. Формат его таков: exportFile(format, to, showOptions, preset), где format – одно из значений нумератора ExportFormat, которое определяет типа файла, to – имя файла для экспорта, showOptions – логическая переменная, которая определяет, будет ли показан диалог экспорта, специфичный для выбранного типа (необязательный параметр, по умолчанию – false), preset – стиль экспорта, актуален пока только для экспорта в формат pdf – Adobe PDF Presets (необязательный параметр).

Вот список доступных свойств нумератора ExportFormat и их десятичные значения:

ExportFormat.epsType – 1952400720

ExportFormat.inCopy – 1768842084

ExportFormat.jpg – 1246775072

ExportFormat.pdfType – 1952403524

ExportFormat.rtf – 1381254688

ExportFormat.svg – 1398163232

ExportFormat.svgCompressed – 1398163267

ExportFormat.taggedText – 1416066168

ExportFormat.textType – 1952412773

ExportFormat.xml – 1481460768

При экспорте текста в текстовые форматы (rtf, taggedText, textType) можно экспортировать как полный текст статьи, используя метод exportFile() объекта story, так и текст отдельного текстового фрейма – метод exportFile() объекта textFrame, или произвольный текст.

storyTextExport.jsx
Скрипт экспортирует в текстовый формат весь текст статьи
with (app) {
  var myDoc = documents.add();
  var myTextFrame = myDoc.pages[0].textFrames.add({visibleBounds: [10,10,60,60]});
// Создаем текстовый фрейм  
  myTextFrame.contents = "Однажды в студеную зимнюю пору я из лесу вышел - был сильный мороз. Гляжу, поднимается медленно в гору лошадка, везущая хворосту воз.";
// Задаем содержимое фрейма
  var myFile = Folder.myDocuments.saveDlg("Выберите файл для сохранения:", "Текстовые файлы:*.txt");
// Выбираем файл для сохранения
  if (!myFile) {
    exit();
  }
  myTextFrame.parentStory.exportFile(ExportFormat.textType, myFile);
// Экспортируем файл
  myFile.execute();
// Выполняем файл
}
textTextExport.jsx
Скрипт экспортирует в текстовый формат произвольно выбранный текст статьи
with (app) {
  var myDoc = documents.add();
  var myTextFrame = myDoc.pages[0].textFrames.add({visibleBounds: [10,10,60,60]});
// Создаем текстовый фрейм  
  myTextFrame.contents = "Однажды в студеную зимнюю пору я из лесу вышел - был сильный мороз. Гляжу, поднимается медленно в гору лошадка, везущая хворосту воз.";
// Задаем содержимое фрейма
  with (myTextFrame.parentStory.insertionPoints) {
    var myText = itemByRange(firstItem(), anyItem());
// Определяем произвольный текст для экспорта
  }
  var myFile = Folder.myDocuments.saveDlg("Выберите файл для сохранения:", "Текстовые файлы:*.txt");
// Выбираем файл для сохранения
  if (!myFile) {
    exit();
  }
  myText.exportFile(ExportFormat.textType, myFile);
// Экспортируем файл
  myFile.execute();
// Выполняем файл
}

При экспорте объектов (в том числе и текстовых) в графические файлы (jpg, pdf, eps, svg) несколько сложнее, потому как при вызове метода exportFile(), например, для объекта типа rectangle экспортируется вся страница (или страницы, если их несколько) документа.

exportRectangle.jsx
Скрипт экспортирует выбранный объект в jpg
with (app) {
  var myDoc = documents.add();
  var myRectangle = myDoc.pages.add().rectangles.add({visibleBounds: [10,10,60,60]});
// Создаем прямоугольник на новой странице
  with (myRectangle) {
    strokeColor = "Black";
    strokeType = "Solid";
// Определяем произвольный текст для экспорта
  }
  var myFile = Folder.myDocuments.saveDlg("Выберите файл для сохранения:", "jpg:*.jpg");
// Выбираем файл для сохранения
  if (!myFile) {
    exit();
  }
  myRectangle.exportFile(ExportFormat.jpg, myFile);
// Экспортируем файл
  myFile.execute();
// Выполняем файл
}

Для того чтобы экспортировать отдельный объект в графический файл, нужно его перед этим выделить и указать в диалоге экспорта, что экспортироваться должен выделенный объект. Не все форматы поддерживают экспорт выделенных объектов, например, eps признает только страницы или развороты.

exportSelectedObject.jsx
Скрипт экспортирует в формат jpg только выделенный объект
with (app) {
  var myDoc = documents.add();
  var myRectangle = myDoc.pages.add().rectangles.add({visibleBounds: [10,10,60,60]});
// Создаем прямоугольник на новой странице
  with (myRectangle) {
    strokeColor = "Black";
    strokeType = "Solid";
// Определяем произвольный текст для экспорта
  }
  var myFile = Folder.myDocuments.saveDlg("Выберите файл для сохранения:", "jpg:*.jpg");
// Выбираем файл для сохранения
  if (!myFile) {
    exit();
  }
  myRectangle.select();
  myRectangle.exportFile(ExportFormat.jpg, myFile, true);
// Экспортируем файл
  myFile.execute();
// Выполняем файл
}

Одной из наиболее популярных задач экспорта является постраничный экспорт документа в pdf. В следующий раз мы напишем скрипт, который будет экспортировать выбранный диапазон страниц постранично в pdf на основе выбранного стиля экспорта.

Полезный скрипт

Пропал кусок текста: «зовут дядя Федор»

Иногда текст не помещается в установленные ему границы. Это не проблема, когда в публикации две-три статьи. Если счет статей идет на десятки, а связанных фреймов – на сотни, то поиск пропавшего хвостика текста – задача нетривиальная, иногда даже опытные корректора пропускают такую неприятную ошибку

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

OFCatcher.jsx
Скрипт для «отлова» переполненных текстовых фреймов.
var langAlertNoDoc = localize({en: "No documents are open!", ru: "Нет открытых документов!"});
var langAlertAllRight = localize({en: "Problem textframes it is not revealed!", ru: "Проблемных текстовых фреймов не обнаружено!"});
var langReportHeader = localize({en: "The report on problem textframes:", ru: "Отчет о проблемных текстовых фреймах:"});
var langReportPage = localize({en: "Page", ru: "Страница"});
var langReportLine = localize({en: "Line", ru: "Строка"});
var iniScriptName = "Overflow catcher";
var iniScriptVersion = "1.0";
var iniReportFile = "ofReport.txt";
var iniLabel = "&&WARNING!";
function getParent (myObject, myType) {
  while (myObject.constructor.name != "Application") {
    if (myObject.constructor.name == myType) {
      return myObject;
    } else {
      myObject = myObject.parent;
    }
  }
  return null;
}
function findDoubles (myArray, myElement) {
  for (var elementCounter = 0; elementCounter < myArray.length; elementCounter++) {
    if (myArray[elementCounter] == myElement) {
      return true;
    }
  }
  return false;
}
function getPrevPageFrame (myTextFrame) {
  try {
    while (myTextFrame)  {
      if (getParent(myTextFrame, "Page") != null) {
        return myTextFrame;
      } else {
        myTextFrame = myTextFrame.previousTextFrame;
      }
    } 
    return null;
  } catch (error) {
    return null;
  }
}
function removeWarnings() {
  var myRectangles = new Array;
  for (var rectCounter = 0; rectCounter < myDoc.rectangles.length; rectCounter++) {
    var myRectangle =  myDoc.rectangles[rectCounter];
    if (myRectangle.extractLabel(iniLabel) == "true") {
      myRectangles.push(myRectangle);
    }
  }
  myRectangles.reverse();
  for (var rectCounter = 0; rectCounter < myRectangles.length; rectCounter++) {
    myRectangles[rectCounter].remove();
  }
}
function setWarnings (myTextFrames) {
  var myResult = iniScriptName + " " + iniScriptVersion + "\nOlegButrin | indesign.rudtp.ru | obutrin@indesign.rudtp.ru\n\n" + langReportHeader + "\n\n";
  for (var frameCounter = 0; frameCounter < myTextFrames.length; frameCounter++) {
    var myFrame = myTextFrames[frameCounter];
    var myPage = getParent(myFrame, "Page");
    var myDoc = getParent(myPage, "Document");  
    myResult += langReportPage + " " + String(myPage.name) + ",\t" + langReportLine + ": \"" + String(myFrame.lines.lastItem().contents)  + "\".\n";
    var myRectanle = myPage.rectangles.add({visibleBounds: myFrame.visibleBounds, strokeWeight: "2pt", strokeType: "Solid", strokeColor: myDoc.colors.item("Magenta"), strokeTint: 100, nonprinting: true, strokeAlignment: StrokeAlignment.outsideAlignment});
    myRectanle.insertLabel(iniLabel, "true");
  }
  return myResult;
}
function getOverFrames(myDoc) {
  var myResult = new Array;
  for (var storyCounter = 0; storyCounter < myDoc.stories.length; storyCounter++) {
    var myStory = myDoc.stories[storyCounter];
    for (var frameCounter = 0; frameCounter < myStory.textFrames.length; frameCounter++) {
      if (getParent(myStory.textFrames[frameCounter], "Page") == null) {
        var myFrame = getPrevPageFrame(myStory.textFrames[frameCounter]);
        if (myFrame != null && !findDoubles (myResult, myFrame)) {
          myResult.push(myFrame);
        }
      }
      if (myStory.overflows) {
        var myFrame = getPrevPageFrame(myStory.textFrames.lastItem());
        if (myFrame != null && !findDoubles (myResult, myFrame)) {
          myResult.push(myFrame);
        }        
      }
    }
  }
  return myResult;
}
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    alert(langAlertNoDoc);
    exit();
  }
  removeWarnings();
  var myFrames = getOverFrames(myDoc);
  if (myFrames.length == 0) {
    alert(langAlertAllRight);
    exit();
  } else {
    var myReport = setWarnings(myFrames);
    var myFile = new File(activeScript.path + "/" + iniReportFile);
    try {
      myFile.open("w");
      myFile.write(myReport);
      myFile.close();
      myFile.execute();
    } catch (error) {
      alert(error);
      exit();
    }
  }
}

Олег Бутрин
THINK.JS выпуск № 13 от 2007-01-29

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход /  Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход /  Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход /  Изменить )

Connecting to %s