Processing:数据输入(下)

25. P5不仅可以分析来自URL的小块信息,还可以分析来自新闻feed、文章、演讲或整本书的大块文本。Daniel引用的txt网站估计是被和谐了,总之我这此时此刻是打不开,因此,我胡乱搜索了一个P5官方版本变更说明的txt作为下例引用的txt地址,同样的,如果你在阅读本笔记的时候无法打开我现在使用的这个在线txt地址的话,你也可以上网自己搜索一个来用:

PFont f;              // 字体变量
String[] revisions;    // 承载所有文本的数组
int counter = 1509;   // 我们在文本文档中的位置

// 把标点和符号用于分界符
String delimiters = " ,.?!;:[]+/()\"-#=_";

void setup() {
  size(200,200);
  
  f = createFont("Lucida Sans", 16);
  
  // 将文本文档载入字符串数组
  String url = "http://processing.org/download/revisions.txt";
  String[] rawtext = loadStrings(url);
  
  // 将数组整合为一个巨长的字符串
  String everything = join(rawtext, "" );
  
  // 文本文档中的所有行先是组合成一个长字符串然后分解成一个独立单子的数组
  // 注意我们使用splitTokens()将全部标点和符号作为分界符.  
  revisions = splitTokens(everything,delimiters);
  frameRate(1);
}

void draw() {
  background(255);
  
  // 从文本文档内取一个词
  String theword = revisions[counter];
  
  // 计数那个词在文本文档中出现的次数
  int total = 0;
  for (int i = 0; i < revisions.length; i ++ ) {
    if (theword.equals(revisions[i])) {
      total ++;
    }
  }
  
  // 显示文本及其出现的总次数
  textFont(f);
  fill(0);
  text(theword,10,90);
  text(total,10,110);
  stroke(0);
  fill(175);
  rect(10,50,total/4,20);
  
  // 继续下一个词
  counter = (counter + 1) % revisions.length;
}

一个好玩的:在上例中我发现,在P5版本变更说明文件中出现频率最高的一个词是"bug",竟然高于"the"和"processing"出现的频率。

26. 练习18-10,找出并显示上文txt文档中字母a~z出现的频率。well,暂时没啥一阵见血的主意,想出来来更新。我的解答见

27. 如我们所见,loadStrings()可以被用于从网页检索原始数据。但是,除非你只需要在整个setup()的过程中载入一次数据,否则问题就来老。比方说,你每5分钟通过XX网站的XML来检索是否有新的种子放出。当你每次调loadStrings(),sketch在接收数据的时候都会停掉,所有动画都会傻逼掉。这是因为loadStrings()是一个“粘连”函数,换句话说,sketch在loadStrings()完成它的任务前都会一直停在那条代码上。如果是一个本地的文本文件,则会很快。但是,一个对于网页的请求是异步的(asynchronous),意味着服务器将自行掌握返回你需要数据的时间。你知道loadStrings()需要多长时间完成自己的任务?不知道,也没有人知道,此刻你在与服务器的较量中明显处于劣势!

28. 于是一个被称为simpleML的库跳出来准备拯救你于水火之中。它可以平行执行sketch并向服务器提出请求,实现sketch多任务化并在数据检索进行中同时继续动画。这里属于高阶应用环节(或者也许几乎不会出现于我们目前的使用中),感兴趣的请自行参考原书或上边那个链接,这里我就偷懒跳过了。

29. 当然,我们可以如之前那个例子一般去手动在XML中搜寻我们想要的数据。但是,一个更方便的方式是使用一个支持XML分析的库。XML用树形结构组织信息。比如一堆学生,每人都有各自的id,名字,电邮,地址什么的,那么他们的XML树看起来就应像下图一般:

学生树
学生树

XML源文件(只以列两名学生为例)即:

<?xml version = "1.0" encoding = "UTF-8 " ?>       
<students>     
           <student>       
                      <id>001</id>        
                      <name>ww</name>        
                      <phone>55-555-5555</phone>     
                      <email>ww@coding.im</email>        
                      <address>      
                               <street>123 Processing Way</street>       
                               <city>Kunming</city>
                               <state>Yunnan</state>        
                               <zip>01234</zip>     
                      </address>       
</student>
<students>     
           <student>       
                      <id>002</id>        
                      <name>sulu</name>        
                      <phone>66-666-6666</phone>     
                      <email>sulu@coding.im</email>        
                      <address>      
                               <street>123 Processing Way</street>       
                               <city>Chenzhou</city>
                               <state>Hunan</state>        
                               <zip>01234</zip>     
                      </address>       
</student>

请注意它与面向对象编程的相似。我们可以如下去想一个XML树。XML文档讲述了一个学生对象的数组。每个学生对象都有很多信息,id,名字,邮址,地址....邮址同样是一个有很多数据的对象,比如接到、城市和邮编。

30. 回到天气的例子,现在我们来重看它的源XML代码(人为简化后):

<?xml version = "1.0" encoding = "UTF-8" standalone = "yes"?>  
<rss version = "2.0" xmlns:yweather = "http://xml.weather.yahoo.com/ns/rss/1.0">
         <channel>      
      <item>       
         <title>Conditions for New York, NY at 3:51 pm EST</title>        
         <geo:lat>40.67< /geo:lat>        
         <geo:long>-73.94</geo:long>      
         <link>http://xml.weather.yahoo.com/forecast/USNY0996_f.html </link>        
         <pubDate>Mon, 20 Feb 2006 3:51 pm EST</pubDate>       
         <yweather:condition text = "Fair"  code = "34" temp = "35"  date = "Mon, 20 Feb 2006 3:51 pm EST " />     
         <yweather:forecast day = "Mon"  date = "20 Feb 2006" low = "25"  high = "37" text = "Clear" code = "31" />     
       </item>           
            </channel>    
</rss>

这个数据结构如果整理成树型的话,如下:

天气树
天气树

31. 接下来,教材又开始讲simpleML库。。同样的,感兴趣的同学请自学,这段跳过。

32. 说半天,Daniel告诉我们simpleML库真的很simple,因为它不能完成一些高级的任务...OK,现在我们还有两个选择:使用Christian Riekoff写的proXML,或者使用P5内建的XML库(我X,你早点说这个会死啊- -b):

import processing.xml.*;

33. 当库被导入后,第一步是创建一个XMLElement对象。这个对象将导入来自网络或本地XML文档的数据。构造器要求两个引数,"this"以及XML的文件名或URL。

String url = "xmldocument.xml"; 
XMLElement xml = new XMLElement(this,url);

34. 这个XML库在载入文档的时候将会让sketch暂停,如果你需要异步同步,则需要去研究proMXL。

35. 一个XMLElement对象代表XML树中的一个元素。当一个文档最先载入,那个元素对象将一直称为根元素。simpleXML库自动遍历整棵树为我们找到正确的信息。但用P5的XML库,我们不得不手动做这个事。尽管这看起来更复杂了,但也意味着同时,我们可以更好、更精确的控制我们的搜索。

36. 回望之前那个天气的树状图,我们可以经由以下路径找到温度:
1)树的根元素是"RSS"。
2)"RSS"有一个子元素名叫"Channel"。
3)"Channel"的第十三个子元素是"item"。(图标被简化为只显示channel的一个子元素)
4)"item"的第六个子元素是“yweather:condition”。
5)温度由属性'temp“表示存于“yweather:condition”中。

37. 一个元素的子元素可以由一个索引(从0开始,与一个数组相同)传递进入getChild()函数来进行访问。一个元素的内容由getContent()检索,属性被读取作任一数字(原文:attributes are read as either numbers,不知道怎么翻译才好,有知道的请联系我更正)—— getIntAttribute(), getFloatAttribute() —或文本— getStringAttribute()

// 访问根元素的第一个子元素 
XMLElement channel = xml.getChild(0);

遵循上一条的1~5步,我们可以搞出如下代码:

XMLElement xml = new XMLElement(this, url); 
XMLElement channel = xml.getChild(0);
// 一个元素的第十三个子元素在索引中列第十二位 
XMLElement item = channel.getChild(12); 
XMLElement condition = item.getChild(5); 
temp = condition.getIntAttribute("temp");

38. 其他有用的能够调用XMLElement对象的函数是:
getChildCount() — 返回一个XMLElement子元素的总数.
getChildren() — 以数组的形式返回XMLElements的全部子元素.

39. 在之前那个气泡的例子中,我们用txt文档中的数字作为气泡的数据。一个XML文档同样具备这样的功能。让我们按颜色来整理一个XML档:

<?xml version = "1.0" ?>
<bubbles>      
  <bubble>       
    <diameter>40</diameter>       
    <color red = "75"  green = "255" />      
  </bubble>       
  <bubble>       
    <diameter>20</diameter>       
    <color red = "255"  green = "75" />      
  </bubble>
  <bubble>       
    <diameter>80</diameter>       
    <color red = "100"  green = "150" />      
  </bubble>
</bubble

40. 我们可以使用getChildren()来接收一个"Bubble"元素的数组并从中制造一个气泡对象。以下为示例(与我们早先使用的Bubble类完全一样,因此在下边的代码里就不写这个类以免吓到你我了):

import processing.xml.*;

// 一个气泡对象数组
Bubble[] bubbles;

void setup() {
  size(200,200);
  smooth();
  // 载入一个XML文档
  XMLElement xml = new XMLElement(this, "bubbles.xml" );
  
  // 使用getChildCount()得到气泡对象的总数.  
  int totalBubbles = xml.getChildCount(); 
  
  // 做个同样大小的数组
  bubbles = new Bubble[totalBubbles];
  
  // 获取全部子元素
  XMLElement[] children = xml.getChildren();
  
  for (int i = 0; i < children.length; i ++ ) {
  // 直径是子元素0
    XMLElement diameterElement = children[i].getChild(0);
    
    // 直径是第一个元素的内容,红和绿是第二个元素的属性
    int diameter = int(diameterElement.getContent());
    
    // 颜色是子元素1
    XMLElement colorElement = children[i].getChild(1);
    int r = colorElement.getIntAttribute("red");
    int g = colorElement.getIntAttribute("green");
    
    // 用XML文档的值创建一个新的气泡对象
    bubbles[i] = new Bubble(r,g,diameter);
  }  
}

void draw() {
  background(255);
  // 显示并移动全部气泡
  for (int i = 0; i < bubbles.length; i++ ) {
    bubbles[i].display();
    bubbles[i].drift();
  }
}

41. 载入HTML和XML文档能够很方便的从网上拽出信息,但是,对于更复杂的程序来说,很多网站都会提供一个API。一个API(Application Programming Interface,应用程序接口)是一个用于软件间访问彼此服务的接口。有很多API可用于P5,你可以在P5官网库页面的“Data / Protocols”环节获取更多信息。

42. 接下来,我们要运用Yahoo应用程序接口来看一个网络搜索的例子。尽管我们可以直接访问Yahoo API,但是这里我们要使用Daniel同学自己写的一个P5库来使事情变得简单一些。介绍和下载在这里

43. Well,如果你和我一样傻逼,在雅虎SDK下载包中没找到那个.jar文件,那么你可以在这里找到它。

44. 你可以在这里注册你的雅虎API Key,请注意,application id = API key

45. 呼呼呼,终于万事俱备了,你安好了库,申请好了API Key,现在来玩个有趣的例子:

import pyahoo.*;

// 创建一个YahooSearch对象. 你需要填入雅虎给你的API key.
YahooSearch yahoo; 

void setup() {
  size(100,100);
  
  // Make a search object, pass in your key
  yahoo = new YahooSearch(this, "用你获得的API Key替换这句话");
}

// 鼠标按下进行搜索
void mousePressed() {
  // 搜索关键词。默认将返回10个结果 
  // 如果你想要更多或更少的结果,你可以写: yahoo.search("oneway post rock post rock china", 30);
  yahoo.search("oneway post rock china"); 
}

void draw() {
  noLoop();
}

// 当搜索完成后
void searchEvent(YahooSearch yahoo) {
  
  // 获取标题和URL
  String[] titles = yahoo.getTitles();
  // 搜索结果以一个字符串数组返回. 
  // 你同样可以用getSummaries()获取摘要.
  String[] sum = yahoo.getSummaries();
  String[] urls = yahoo.getUrls(); 
  
  for (int i = 0; i < titles.length; i++) {
    println( "__________" );
    println(titles[i]);
    println(sum[i]);
    println(urls[i]);
  }  
}

如何?在p5的环境中建立自己的yahoo搜索引擎的感觉怎么样?

46. 这个库还可以用于做简单的视觉(样图)。接下来的例子搜索五个名字并为每个名字画一个圆(大小与搜索结果捆绑):

import pyahoo.*;

YahooSearch yahoo;
PFont f;

// 欲搜索的人名,一个“气泡”对象的数组
String[] names = { "sulu" , "Cobain" , "Axl" , "Wang" , "Wong" };
Bubble[] bubbles = new Bubble[names.length];

int searchCount = 0;

void setup() {
  size(500,300);
  yahoo = new YahooSearch(this, "用你获得的API Key替换这句话" );
  f = createFont("Lucida", 20, true);
  textFont(f);
  smooth();
  
  // 搜索全部名字
  for (int i = 0; i < names.length; i++) {
    // search()函数在数组中被每个名字调用
    yahoo.search(names[i]); 
  }
}

void draw() {
  background(255);
  
  // 显示全部气泡
  for (int i = 0; i < searchCount; i++) {
    bubbles[i].display();
  }
}

// 搜索一次进行一个
void searchEvent(YahooSearch yahoo) {
  
  // 每个搜索结果的总数
  // getTotalResultsAvailable()返回雅虎每个搜索结果的总数. 
  // 这些数字也许可以非常大,因此在作为一个圆的大小前要被按比例缩小.
  int total = yahoo.getTotalResultsAvailable(); 
  
  // 缩小总数使其可见
  float r = sqrt(total)/75;
  
  // 创建一个新的气泡对象
  // 搜索数据被用于使一个气泡对象可见.
  Bubble b = new Bubble(yahoo.getSearchString(), r,50 + searchCount*100,height/2);
  bubbles[searchCount] = b;
  searchCount++;
}

// 简单的描述搜索结果的"Bubble"类
class Bubble {
  
  String search;
  float x,y,r;
  
  Bubble(String search_, float r_, float x_, float y_) {
    search = search_;
    r = r_;
    x = x_;
    y = y_;
  }
  
  void display() {
    stroke(0);
    fill(0,50);
    ellipse(x, y, r, r);
    textAlign(CENTER);
    fill(0);
    text(search,x,y);
  }
}

如上可见,关键字“Wong”比“Wang”返回的结果要多,可见装逼犯还是广泛存在的。

47. 我们可以方便的在本地调用网络数据,但当你将你的程序作为一个浏览器的applet使用时,有一些安全方面的要求。
1)如果applet存于你的站点:http://www.我的域名.com,那可以使用没问题:

// 将能在你的浏览器里工作
String[] lines = loadStrings( "http://www.我的域名.com/数据.html");

2)但是,如果你要求一个来自不同域的URL,你就不那么走运了:

// 将无法在你的浏览器里工作
String[] lines = loadStrings( "http://www.立花里子的域名.com/数据.htm");

48. 关于此点的一个解决办法是,在你的服务器上创建一个代理脚本与你的applet共存,链接外部URL并将信息传递回applet —— 实际上,你是在忽悠你的applet,让它认为它正在检索的只有本地信息。

49. 另一个办法是为你的applet签名。签名一个小程序即说“你好,我叫ww,我制作了这个小程序。如果你信任我请允许我访问它通常不让别人访问的的资源”的过程。

50. 如果你对以上问题感兴趣或者恰巧在使用中需要解决它们,可以访问这里以获取更多的提示。

Be Sociable, Share!

发表评论

电子邮件地址不会被公开。 必填项已用*标注