Think.JS №09

  • Для начинающих: Стильное решение
  • Полевые испытания: От версии к версии
  • Учим матчасть: Файлы – надежные источники данных
  • Полезный скрипт: Практическое перекодирование файлов

Содержание

Для начинающих: Стильное решение

Полевые испытания: От версии к версии

Учим матчасть: Файлы – надежные источники данных

Полезный скрипт: Практическое перекодирование файлов

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

Стильное решение

Стили в программах верстки – сами по себе большое подспорье в ускорении процесса. Причем неоспоримым преимуществом InDesign перед многими другими программами является понимание разработчиками важности этого средства. В PageMaker, насколько я помню из темного прошлого, не было соответствия стилям знака (characterStyles), что заметно затрудняло работу с разными оформлениями текста одного абзаца. О таких роскошествах, как шорткаты на стили, вообще не приходилось и мечтать – а теперь трудно представить себе, как без этого обходились верстальщики.

В InDesign идея использования стилей эволюционирует от версии к версии. В CS2 появилась возможность создавать и использовать стили объектов – решение, значимость которого трудно переоценить. В следующей версии, как сообщают компетентные граждане, мы будем иметь возможность создавать и использовать стили таблиц и ячеек как штатную возможность – без обращения к сторонним плагинам. Если в релизе эта возможность будет удобнее для написания скриптов, чем в SmartStyles (который обладает интерфейсом для использования скриптов, но крайне ненадежным) или в Table\Cell Styles от Teacup (который также обладает соответствующим скриптовым интерфейсом, но очень слаб по возможностям), то компании Adobe мы скажем искреннее спасибо.

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

Одним из главных свойств стилей (помимо основного свойства – хранения настроек) я считаю возможность наследования. Эта тема требует отдельной подробной статьи, поскольку правильная иерархия стилей в документе уже сама по себе является мощным свойством автоматизации процесса верстки. В этот раз мы займемся работой со стилями абзаца и знака – paragraphStyles и characterStyles.

Коллекция paragraphStyles является свойством объекта типа document. При создании нового документа в InDesign CS2 автоматически создается два стиля абзаца – [No Paragraph Style] и [Basic Paragraph] . В CS создавался только один — [No Paragraph Style], который являлся одновременно базовым и мог быть изменен. В CS2 стиль [No Paragraph Style] объявлен root-стилем и защищен от изменения из скриптов, а пользователю через интерфейс вовсе недоступен. Логика такого решения не совсем понятна, тем более что со стилями знака дело обстоит одинаково в обоих релизах. Видимо, в новой версии InDesign будут какие-то изменения и для стилей знака – тогда, возможно, разрешится и этот вопрос. Создаются новые стили, как и элементы многих других коллекций при помощи метода add().

addParaStyle.jsx
Скрипт создает в активном документе новый стиль абзаца
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit()
  }
  var myStyle = myDoc.paragraphStyles.add();
}

При создании стиля можно не передавать никаких параметров – стиль все равно будет создан. Позже можно настроить свойства стиля напрямую, обращаясь к ним, но можно передать некоторые параметры при создании для ускорения работы.

addParaStyle.jsx
Скрипт создает в активном документе новый стиль абзаца и одновременно меняет настройки
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit()
  }
  var myStyle = myDoc.paragraphStyles.add({name: "Text 1", pointSize: 9});
}

Остальные настройки стиля, если их не редактировать, будут соответствовать настройкам root-стиля [No Paragraph Style]. Используя свойство paragraphStyle.basedOn, можно «переключить» неуказанные напрямую свойства на значения другого стиля.

addParaStyleBasedOn.jsx
Скрипт создает в активном документе новый стиль абзаца на основе существующего
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit()
  }
  var myStyle = myDoc.paragraphStyles.add({name: "Text 1", pointSize: 9, basedOn: myDoc.paragraphStyles[1]});
}

Такое свойство стиля абзаца (и знака тоже) являются хорошей заменой методу duplicate(), который не предусмотрен. Одновременно это и средство выстраивания иерархии стилей – изменив базовый, можно без труда изменить те свойства, которые в конкретном стиле не были переопределены.

Хорошо, когда иерархия стилей сделана правильно. Если нет, то с редактированием стилей могут быть проблемы. Например, если не во всех стилях указан русский язык, то могут быть проблемы с переносами и типографскими кавычками (это как минимум). Вручную перебрать несколько десятков стилей – это задача, способная взбесить даже самого уравновешенного человека. А человек, знакомый с написанием скриптов, пойдет другим путем и за пару минут сделает скрипт, который моментально изменит язык стиля на требуемый.

changeParaLang.jsx
Скрипт меняет настройки языка во всех стилях параграфа
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit()
  }
  for (var counter = 1; counter < myDoc.paragraphStyles.length; counter++) {
    myDoc.paragraphStyles[counter].appliedLanguage = "Russian"
  }
}

На самом деле, обращение к языку, а точнее, к объекту Language несколько сложнее, но в InDesign CS2 вполне пройдет и такое. На использовании различных языков мы подробнее остановимся в другой раз.

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

changeParaFont.jsx
Скрипт меняет во всех стилях параграфа один шрифт на другой
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit()
  }
  for (var counter = 1; counter < myDoc.paragraphStyles.length; counter++) {
    try {
      var myFont = myDoc.paragraphStyles[counter].appliedFont.fontFamily;
      if ( myFont == "Minion") {
        myDoc.paragraphStyles[counter].appliedFont = "Minion Pro";
      }
    } catch (error) {

    }
  }
}

Создание и работа со стилями знака ничем принципиально не отличаются от создания и работы со стилями абзаца, поэтому рассматривать отдельно их не имеет смысла. Разница есть только при применении стилей к текстам. Вообще, в InDesign есть два пути применить стиль к текстовому объекту: воспользоваться методом applyStyle() или использовать свойства appliedParagraphStyle и appliedParagraphStyle. Использование метода applyStyle() – универсальное средство, в качестве параметра этому методу передается стиль абзаца или стиль знака, – а потому предпочтительнее. Свойства appliedParagraphStyle и appliedCharacterStyle больше подходят для получения стиля, который уже был применен к тексту. Дополнительным (необязательным) параметром метода applyStyle() является логическая переменная, removeOverrides. При значении false сохраняется локальное форматирование текстового объекта (например, вручную настроенный размер шрифта отдельного слова в абзаце при применении к последнему абзацного стиля). При значении true – форматирование текстового объекта безусловно приводится к состоянию, определенному в стиле.

applyCharStyle.jsx
Скрипт демонстрирует применение стиля знака к выделенному тексту
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit()
  }
  if (selection[0].constructor.name != "Text") {
    exit();
  }
  var myText = selection[0];
  try {
    var myCharStyle = myDoc.characterStyles.add({name: "Bold", fontStyle: "Bold"});
  } catch (error) {
    var myCharStyle = myDoc.characterStyles.item("Bold");
  }
  myText.applyStyle(myCharStyle);
}

Конструкция try..catch рекомендуется к использованию при создании стилей потому, что при попытке создать стиль с именем уже существующего стиля, скрипт завершится с ошибкой.

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

applyParaStyle.jsx
Скрипт демонстрирует применение стиля абзаца к абзацу, в котором находится выделенный текст.
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit()
  }
  if (selection[0].constructor.name != "Text") {
    exit();
  }
  var myText = selection[0];
  try {
    var myStyle = myDoc.paragraphStyles.add({name: "Bold", fontStyle: "Bold"});
  } catch (error) {
    var myStyle = myDoc.paragraphStyles.item("Bold");
  }
  if (myText.appliedParagraphStyle != myStyle) {
    myText.applyStyle(myStyle);
  }
}

Объект типа textFrame не имеет свойств appliedCharacterStyle и appliedParagraphStyle, а также метода applyStyle, хотя вручную выделив текстовый фрейм (если он не имеет связанных фреймов) можно применить к нему стиль знака или абзаца. Приходится искать обходной путь – это несложно, но важно знать, как делать. Для этого нужно получить текст всего текстового фрейма методом itemByRange().

applyParaStyleToFrame.jsx
Скрипт демонстрирует применение стиля абзаца к текстовому фрейму
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  try {
    var myTextFrame = selection[0].parentTextFrames[0];
  } catch (error) {
    exit();
  }
  try {
    var myStyle = myDoc.paragraphStyles.add({name: "Bold", fontStyle: "Bold"});
  } catch (error) {
    var myStyle = myDoc.paragraphStyles.item("Bold");
  }
  var myText = myTextFrame.insertionPoints.itemByRange(0, myTextFrame.insertionPoints.length - 1);
  if (myText.appliedParagraphStyle != myStyle) {
    myText.applyStyle(myStyle);
  }
}

А вот объект типа story имеет свойства для определения примененных стилей и метод applyStyle(). При разном оформлении текста в статье свойство story.appliedParagraphStyle определяется по соответствующему свойству первого абзаца статьи, story.appliedCharacterStyle – по соответствующему свойству первого знака (первый объект insertionPoint, если к нему и применен стиль знака, не влияет на свойство).

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

От версии к версии

Интерфейс поддержки скриптов в InDesignCS и InDesign CS2 имеют некоторые различия, поэтому некоторые полезные скрипты, написанные для CS в CS2 работают неправильно, если вообще запускаются. Исправление таких скриптов не всегда целесообразно – некоторые скрипты легче переписать заново, нежели исправить, поскольку работа творческая привлекает больше, чем кропотливая отладка. Но знать методы исправления нужно – для этого достаточно узнать различия в поддержке скриптов в CS и CS2.

В первую очередь в исправлении нуждается обращение к объекту parentTextFrame. Это свойство отсутствует в текстовых объектах CS2 – оно заменено на свойство parentTextFrames, массив текстовых фреймов. Некоторые скрипты в CS обрабатывают все текстовые фреймы – для их поиска приходилось писать дополнительный код. В CS2 это делается проще и легче, поэтому такие куски кода желательно переписать.

Второй момент не так очевиден, как parentTextFrame, поскольку относится не к объектам InDesign, а собственно к реализации JavaScript, точнее к определению области видимости переменных. В InDesign CS был вполне допустим следующий код.

variablesCircle.jsx
Скрипт демонстрирует возможность безопасного использования переменных в цикле в InDesign CS
function decrement() {
  counter = 10;
}
function increment () {
  for (counter = 0; counter < 5; counter++) {
    decrement();
    alert("Значение во время выполнения цикла: " + String(counter));
  }
}
with (app) {
  increment();
  alert("Значение после выполнения цикла: " + String(counter));
}

В CS2 этот пример использовать не рекомендуется во избежание зацикливания и зависания скрипта. Смысл в том, что цикл в CS выполняется независимо от конкретного значения переменной counter определенное количество раз (вычисление происходит в момент запуска цикла). В CS2 такой номер не проходит. Для решения этой проблемы достаточно в каждом цикле использовать служебное слово var.

varVariable.jsx
Безопасное применение одноименных переменных
function decrement() {
  var counter = 10;
}
function increment () {
  for (var counter = 0; counter < 5; counter++) {
    decrement();
    alert("Значение во время выполнения цикла: " + String(counter));
  }
}
with (app) {
  var counter = 20;
  increment();
  alert("Значение после выполнения цикла: " + String(counter));
}

Если переменные определять при помощи var, то область видимости переменной будет ограничена функцией или оператором, в которой она была определена, поэтому одноименные переменные существуют «параллельно», не перезаписывая значения.

Третье отличие CS от CS2 в том, что во втором релизе более строг подход к передаче объектов InDesign в качестве параметра методам. Например, для перекрашивания объектов в CS достаточно было передать в качестве параметра имя объекта типа color или swatch, а в CS2 требуется передавать сам объект, который, впрочем, можно получить при помощи метода item().

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

Файлы – надежные источники данных

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

Для чего может понадобиться перекодировщик текстовых файлов? Если вы задались таким вопросом, значит вам никогда не приходилось работать с файлами в кодировке, которую InDesign не хочет понимать штатными средствами (см. сегодняшний Полезный скрипт). Между тем способность авторов к творческому поиску недооценивать нельзя. Встречаются индивиды, которые обожают текстовый редактор Лексикон для DOS и не желают иметь дело ни с чем другим. Другие собственноручно оформляют тексты в html и сохраняют их в кодировке KOI8-R, будучи искренне при этом уверены, что оказали верстальщику огромную помощь. Конечно, современные текстовые редакторы работают с разными кодировками, но обработка файлов все равно требует значительного времени. А с помощью InDesign текстовые файлы перекодировать проще простого. Для этого нужно воспользоваться свойством encoding файла.

В InDesign встроена поддержка большого количества кодировок. За некоторыми перекодировками InDesign может обратиться к операционной системе. Полный список доступных кодировок можно узнать в документации Adobe.

codeFile.jsx
Скрипт демонстрирует работу функции по перекодированию из одной кодировки в другую
function codeFile (myReadFile, myWriteFile, myReadEncoding, myWriteEncoding) {
  if (myReadEncoding == undefined) {
    myReadEncoding = myReadFile.encoding;
  } else {
    myReadFile.encoding = myReadEncoding;
  }
  if (myWriteEncoding == undefined) {
    myWriteEncoding = myReadFile.encoding;
  }
  myWriteFile.encoding = myWriteEncoding;
  var myString = "";
  try {
    myReadFile.open("r");
    while (!myReadFile.eof) {
      myString += myReadFile.readln() + "\n";
    }
    myReadFile.close();
    myWriteFile.open("w");
    myWriteFile.write(myString);
    myWriteFile.close();
  } catch (error) {
    return false;
  }
  return true;
}
with (app) {
  codeFile(activeScript, File(activeScript.path + "/coded_" + activeScript.name), undefined, "UTF16")
}

Разберем работу функции codeFile. В качестве параметров функция получает имя перекодируемого файла, имя файла для сохранения, кодировку перекодируемого файла и кодировку для сохранения. Последние два параметра могут быть определены как undefined – в этом случае используется текущая кодировка перекодируемого файла (как ее определяет InDesign). Вначале функция проверяет, указаны ли кодировки при вызове и изменяет свойства encoding в соответствии. Затем в блоке try..catch производится построчное считывание исходного файла в текстовую переменную и запись в конечный файл. Если операции чтения и записи невозможно выполнить, функция возвращает false, при успешном выполнении – true.

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

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

Можно пойти по легкому пути, попытавшись сохранить значения переменных в текстовом файле в виде «var myNum = 10». Затем стандартными методами полученный файл можно подключить к скрипту на стадии написания. Но это тоже не наш метод, поскольку имя переменной получить гораздо сложнее, чем ее значение (если вообще возможно). Если в скрипте используется 20 переменных, требующих сохранения значений в файл, скрипт станет на 20 строк длиннее. Если потребуется добавить еще 10 переменных – скрипт опять придется редактировать. Однако есть способ одновременного хранения значений переменных и доступа к их именам. Это простые объекты.

variablesObject.jsx
Скрипт демонстрирует возможности редактирования и получения свойств объекта типа Object
with (app) {
  var variables = new Object();
  variables.smallNudge = 10;
  variables.largeNudge = 50;
  for (prop in variables) {
    alert(prop + " = " + variables[prop]);
  }
}

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

saveVariables.jsx
Скрипт демонстрирует работу объекта для сохранения переменных скрипта.
function variables () {
  return true;
}
variables.prototype.getValue = function (prop, val) {
  if (this[prop] == undefined) {
    this[prop] = val;
  }
  return this[prop];
}
variables.prototype.load = function (myFile) {
  if (!myFile.exists) {
    return null;
  }
  try {
    myFile.open("r");
    while (!myFile.eof) {
      var myData = myFile.readln().split(" = ");
      if (myData.length < 2) {
        continue;
      }
      this[myData[0]] = myData[1];
      if (String(Number(this[myData[0]])) == this[myData[0]]) {
        this[myData[0]] = Number(this[myData[0]]);
      }
      if (this[myData[0]] == "true") {
        this[myData[0]] = true;
      }
      if (this[myData[0]] == "false") {
        this[myData[0]] = false;
      }
    }
    myFile.close();
  } catch (error) {
    return false;
  }
  return true;
}
variables.prototype.save = function (myFile) {
  var myString = "";
  for (prop in this) {
    if (this[prop].constructor.name != "Function") {
      myString += prop + " = " + this[prop] + "\n";
    }
  }
  try {
    myFile.open("w");
    myFile.write(myString);
    myFile.close();
  } catch (error) {
    return false;
  }
  return true;
}
with (app) {
  var iniFile = File(activeScript.path + "/tmp.ini");
  var myData = new variables();
  myData.load(iniFile);
  var myDialog = dialogs.add();
  with (myDialog.dialogColumns.add()) {
    var useDrop = dialogRows.add().dropdowns.add({stringList: ["Один", "Два", "Три"], selectedIndex: myData.getValue("drop", 0)});
    var useText = dialogRows.add().textEditboxes.add({editContents: myData.getValue("text", "")});
    var useCheck = dialogRows.add().checkboxControls.add({staticLabel: "Переключатель", checkedState: myData.getValue("check", true)});
  }
  var myResult = myDialog.show();
  if (!myResult) {
    exit();
  }
  var myFolder = Folder.selectDialog("Выберите папку", myData.getValue("folder", ""));
  if (!myFolder) {
    myFolder = Folder();
  }
  var myDrop = myData["drop"] = useDrop.selectedIndex;
  var myText = myData["text"] = useText.editContents;
  var myCheck = myData["check"] = useCheck.checkedState;
  myData["folder"] = myFolder.path + "/" + myFolder.name;
  myData.save(iniFile);
}

Разберем свойства объекта. Функция-конструктор variables() не делает ничего – она нужна для объявления объекта. Функция getValue() получает при вызове два параметра – имя свойства и значение по умолчанию. Если свойство с таким именем не определено в объекте, то оно создается равным значению по умолчанию.

Функция load() служит для чтения ранее сохраненных данных из файла, который передается ей в качестве параметра. Каждая строка файла при чтении преобразуется в массив методом split() для того, чтобы получить имя переменной и ее значение. Значения подвергаются простому преобразованию (не всегда надежному, но подходящему для большинства случаев) для получения из текстового значения чисел или логических переменных. Функция возвращает true при нормальном завершении работы и false – при проблемах с чтением файла.

Функция save() предназначена для сохранения свойств объекта в файл. Так как объект содержит не только свойства, но и методы, используется проверка на тип Function (функции не сохраняются).

Созданный объект прекрасно справляется с сохранением выбора пользователя в диалоговом окне и в диалоге выбора папки.

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

Практическое перекодирование файлов

Удивительно, как живучи бывают компьютерные технологии. Газета, в которой я работал в 2004 году, использовала локальную сеть на основе Novell Netware 3, которая была установлена и настроена в 1995 году. Единственное изменение, произошедшее за девять лет работы – это замена коаксиального кабеля на витую пару. На сервере был установлен Word 5.0 (радикально черного цвета), в котором набирались все текстовые материалы газеты.

Если PageMaker 6.5 воспринимал текст, набранный в Word 5.0, имея для этого специальный фильтр, то в InDesign разработчики не посчитали нужным встроить поддержку этого монстра доисторической эпохи. Долгое время приходилось пользоваться методом copy-paste, но это было крайне неудобно. Чтобы раз и навсегда решить проблему с помещением в InDesign текстовых файлов в кодировке DOS, а также любых других кодировках, которые недоступны при импорте текстовых файлов, был написан скрипт placeText.jsx. Скрипт построчно считывает файл в указанной кодировке и помещает его в качестве значения в свойство contents выбранной статьи. Одновременно происходит несложная обработка текста при помощи функции textProcess, которая легко модифицируется.

placeText.jsx
Скрипт для помещения в InDesign файлов в нестандартных кодировках
var iniCodePage = "CP866";
var iniTextClear = true;
var iniExtention = "MS Word 5.0:*.doc, Text files:*.txt, All files:*";
var iniStartFolder = "~/My%20Documents/";
var langNoDoc = {en: "No documents are open!", ru: "Нет открытых документов!"};
var langNoText = {en: "No text object are selected!", ru: "Не выбран текстовый объект!"};
var langSelectFile = {en: "Select file...", ru: "Выберите файл..."};
var langNoFile = {en: "File are not selected!", ru: "Файл не был выбран!"};
var langError = {en: "Script execution error! Error description:", ru: "Произошла ошибка! Описание ошибки:"};
function textProcess(myString) {
//  замена пустых строк
  var myReg = new RegExp("\u000A+", "gim");
  myString = myString.replace (myReg, "\u000D");
//  замена пробелов
  var myReg = new RegExp("\u0020+", "gim");
  myString = myString.replace (myReg, "\u0020");
//  замена тирушек
  var myReg = new RegExp("\u0020\\u002D\u0020", "gim");
  myString = myString.replace (myReg, "\u0020\u2013\u0020");
//  замена тирушек в начале абзаца
  var myReg = new RegExp("\u000D\u002D\u0020", "gim");
  myString = myString.replace (myReg, "\u000D\u2013\u0020");
//  замена три точки на многоточие
  var myReg = new RegExp("\\u002E{3,}", "gim");
  myString = myString.replace (myReg, "\u2026");
  return myString;
}
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    alert(langNoDoc);
    exit();
  }
  try {
    var myStory = selection[0].parentStory;
  } catch (error) {
    alert(langNoText);
    exit();
  }
  var myStartFolder = new Folder(iniStartFolder);
  myFile = myStartFolder.openDlg(langSelectFile, iniExtention);
  if ((!myFile) || (myFile.constructor.name != "File")) {
    alert(langNoFile);
    exit();
  }
  try {
    myFile.encoding = iniCodePage;
    myFile.open("r");
    var myString = myFile.read();
    if (iniTextClear) {
      myString = textProcess(myString);
    }
    myFile.close();
    myStory.contents = "";
    myStory.contents = myString;
  } catch (error) {
    alert(langError + " " + String(error));
    exit();
  }
}

Олег Бутрин
THINK.JS выпуск № 9 от 2006-12-25

 

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

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

Логотип WordPress.com

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

Фотография Twitter

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

Фотография Facebook

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

Connecting to %s