我如何构建自己的 KeyEvent 对象,使其与最终用户键入内容时从 KeyListener 接收到的对象完美(或非常接近)匹配?
例如,我有一个英国 ISO 键盘布局,要键入 " 字符,我按 Shift+2。如果我在 JFrame 上记录它 使用 KeyListener,我收到以下事件:
java.awt.event.KeyEvent[KEY_PRESSED,keyCode=16,keyText=Shift,keyChar=Undefined keyChar,modifiers=Shift,extModifiers=Shift,keyLocation=KEY_LOCATION_LEFT,rawCode=16,primaryLevelUnicode=0,scancode=42,extendedKeyCode=0x10] on frame0
java.awt.event.KeyEvent[KEY_PRESSED,keyCode=50,keyText=2,keyChar='"',modifiers=Shift,extModifiers=Shift,keyLocation=KEY_LOCATION_STANDARD,rawCode=50,primaryLevelUnicode=50,scancode=3,extendedKeyCode=0x32] on frame0
java.awt.event.KeyEvent[KEY_TYPED,keyCode=0,keyText=Unknown keyCode: 0x0,keyChar='"',modifiers=Shift,extModifiers=Shift,keyLocation=KEY_LOCATION_UNKNOWN,rawCode=0,primaryLevelUnicode=0,scancode=0,extendedKeyCode=0x0] on frame0
java.awt.event.KeyEvent[KEY_RELEASED,keyCode=16,keyText=Shift,keyChar=Undefined keyChar,keyLocation=KEY_LOCATION_LEFT,rawCode=16,primaryLevelUnicode=0,scancode=42,extendedKeyCode=0x10] on frame0
java.awt.event.KeyEvent[KEY_RELEASED,keyCode=50,keyText=2,keyChar='"',keyLocation=KEY_LOCATION_STANDARD,rawCode=50,primaryLevelUnicode=50,scancode=3,extendedKeyCode=0x32] on frame0
我想创建一个方法,将 " 作为 char 参数,它会返回上面列出的 KeyEvents 数组。
我的问题是:
在 KEY_PRESSED 和 KEY_RELEASED 事件中,keyChar='"' 表示被按下的字符 ("),但是 keyCode=50 指的是“未移位”的 ASCII 值(又名 2)。我需要知道如何仅从 " 字符获取此非移位值。
对于不同的键盘布局,这个非移位值也会不同。例如,美国 ANSI 布局需要 Shift+' 来键入 " 键,这意味着 keyCode 将是 39 而不是 50。
在某些键盘布局上,需要使用 Shift 键才能键入某个键,但在其他键盘布局上则不需要。例如,# 字符在美国 ANSI 键盘上需要 Shift+3,但在英国 ISO 键盘上不需要按 shift 键。我需要知道我是否应该模拟 shift 按下/释放事件并提供 shift 修饰符。
任何有关如何解决这些问题的见解都将不胜感激。我还应该注意,在我的情况下不能使用 Robot 类。
最佳答案
没有“简单”的方法可以将虚拟键转换为实际键序列或再转换回来,至少我找不到。
调度关键事件的两种主要方式是通过 java.awt.Robot 或直接通过系统事件队列。您要使用哪个取决于您要实现的目标。
组件通常无法区分从键盘发送的击键和您自己生成的击键。
下面的示例比较复杂,很抱歉,我没有找到更好的方法来实现我需要的要求。
public class TestKeyEvents {
public static void main(String[] args) {
new TestKeyEvents();
}
public TestKeyEvents() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ex) {
}
JFrame frame = new JFrame("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
new Thread(new KeyDispatcher()).start();
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new BorderLayout());
JTextArea area = new JTextArea(10, 30);
area.setWrapStyleWord(true);
area.setLineWrap(true);
add(area);
}
}
public class KeyDispatcher implements Runnable {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
}
dispatchKeyEventsViaEventQueue();
dispatchKeyEventsViaRobot();
}
protected void dispatchKeyEventsViaEventQueue() {
if (EventQueue.isDispatchThread()) {
String text = "This is a key sequence dispatched via the event queue\n";
KeySequence keySequence = getKeySequence(text);
List<KeyEvent> events = new ArrayList<>();
List<Integer> modifers = new ArrayList<>();
for (Key key : keySequence) {
events.clear();
System.out.println(key);
switch (key.getStrokeType()) {
case Press:
switch (key.getKeyCode()) {
case KeyEvent.VK_SHIFT:
case KeyEvent.VK_ALT:
case KeyEvent.VK_CONTROL:
case KeyEvent.VK_META:
if (!modifers.contains(key.getKeyCode())) {
modifers.add(key.getKeyCode());
}
break;
default:
events.add(new KeyEvent(new JPanel(), KeyEvent.KEY_PRESSED, System.currentTimeMillis(), getModifiers(modifers), key.getKeyCode(), key.getKeyChar()));
break;
}
break;
case Release:
switch (key.getKeyCode()) {
case KeyEvent.VK_SHIFT:
case KeyEvent.VK_ALT:
case KeyEvent.VK_CONTROL:
case KeyEvent.VK_META:
if (!modifers.contains(key.getKeyCode())) {
modifers.remove(key.getKeyCode());
}
break;
default:
events.add(new KeyEvent(new JPanel(), KeyEvent.KEY_RELEASED, System.currentTimeMillis(), getModifiers(modifers), key.getKeyCode(), key.getKeyChar()));
break;
}
break;
case Type:
events.add(new KeyEvent(new JPanel(), KeyEvent.KEY_PRESSED, System.currentTimeMillis(), getModifiers(modifers), key.getKeyCode(), key.getKeyChar()));
events.add(new KeyEvent(new JPanel(), KeyEvent.KEY_RELEASED, System.currentTimeMillis(), getModifiers(modifers), key.getKeyCode(), key.getKeyChar()));
events.add(new KeyEvent(new JPanel(), KeyEvent.KEY_TYPED, System.currentTimeMillis(), getModifiers(modifers), KeyEvent.VK_UNDEFINED, key.getKeyChar()));
break;
}
for (KeyEvent evt : events) {
Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(evt);
}
}
} else {
try {
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
dispatchKeyEventsViaEventQueue();
}
});
} catch (Exception exp) {
exp.printStackTrace();
}
}
}
protected void dispatchKeyEventsViaRobot() {
try {
Robot robot = new Robot();
String text = "This is a key sequence dispatched via java.awt.Robot\n";
KeySequence keySequence = getKeySequence(text);
List<KeyEvent> events = new ArrayList<>();
for (Key key : keySequence) {
events.clear();
System.out.println(key);
switch (key.getStrokeType()) {
case Press:
robot.keyPress(key.getKeyCode());
break;
case Release:
robot.keyRelease(key.getKeyCode());
break;
case Type:
robot.keyPress(key.getKeyCode());
robot.keyRelease(key.getKeyCode());
break;
}
}
} catch (AWTException exp) {
exp.printStackTrace();
}
}
}
protected int getModifiers(List<Integer> mods) {
int result = 0;
for (int mod : mods) {
result &= mod;
}
return result;
}
public static class Key {
public enum StrokeType {
Type,
Press,
Release
}
private StrokeType strokeType;
private int keyCode;
private char keyChar;
public Key(StrokeType type, int keyCode, char keyChar) {
this.strokeType = type;
this.keyCode = keyCode;
this.keyChar = keyChar;
}
public StrokeType getStrokeType() {
return strokeType;
}
public int getKeyCode() {
return keyCode;
}
public char getKeyChar() {
return keyChar;
}
@Override
public String toString() {
return getStrokeType().name() + " " + getKeyChar() + " (" + getKeyCode() + ")";
}
}
public static KeySequence getKeySequence(String text) {
KeySequence ks = new KeySequence();
for (char c : text.toCharArray()) {
addKeySequence(ks, c);
}
return ks;
}
public static void addKeySequence(KeySequence ks, char character) {
switch (character) {
case 'a':
ks.type(KeyEvent.VK_A, character);
break;
case 'b':
ks.type(KeyEvent.VK_B, character);
break;
case 'c':
ks.type(KeyEvent.VK_C, character);
break;
case 'd':
ks.type(KeyEvent.VK_D, character);
break;
case 'e':
ks.type(KeyEvent.VK_E, character);
break;
case 'f':
ks.type(KeyEvent.VK_F, character);
break;
case 'g':
ks.type(KeyEvent.VK_G, character);
break;
case 'h':
ks.type(KeyEvent.VK_H, character);
break;
case 'i':
ks.type(KeyEvent.VK_I, character);
break;
case 'j':
ks.type(KeyEvent.VK_J, character);
break;
case 'k':
ks.type(KeyEvent.VK_K, character);
break;
case 'l':
ks.type(KeyEvent.VK_L, character);
break;
case 'm':
ks.type(KeyEvent.VK_M, character);
break;
case 'n':
ks.type(KeyEvent.VK_N, character);
break;
case 'o':
ks.type(KeyEvent.VK_O, character);
break;
case 'p':
ks.type(KeyEvent.VK_P, character);
break;
case 'q':
ks.type(KeyEvent.VK_Q, character);
break;
case 'r':
ks.type(KeyEvent.VK_R, character);
break;
case 's':
ks.type(KeyEvent.VK_S, character);
break;
case 't':
ks.type(KeyEvent.VK_T, character);
break;
case 'u':
ks.type(KeyEvent.VK_U, character);
break;
case 'v':
ks.type(KeyEvent.VK_V, character);
break;
case 'w':
ks.type(KeyEvent.VK_W, character);
break;
case 'x':
ks.type(KeyEvent.VK_X, character);
break;
case 'y':
ks.type(KeyEvent.VK_Y, character);
break;
case 'z':
ks.type(KeyEvent.VK_Z, character);
break;
case 'A':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_A, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case 'B':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_B, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case 'C':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_C, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case 'D':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_D, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case 'E':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_E, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case 'F':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_F, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case 'G':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_G, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case 'H':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_H, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case 'I':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_I, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case 'J':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_J, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case 'K':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_K, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case 'L':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_L, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case 'M':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_M, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case 'N':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_N, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case 'O':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_O, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case 'P':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_P, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case 'Q':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_Q, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case 'R':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_R, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case 'S':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_S, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case 'T':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_T, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case 'U':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_U, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case 'V':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_V, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case 'W':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_W, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case 'X':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_X, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case 'Y':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_Y, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case 'Z':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_Z, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case '`':
ks.type(KeyEvent.VK_BACK_QUOTE, character);
break;
case '0':
ks.type(KeyEvent.VK_0, character);
break;
case '1':
ks.type(KeyEvent.VK_1, character);
break;
case '2':
ks.type(KeyEvent.VK_2, character);
break;
case '3':
ks.type(KeyEvent.VK_3, character);
break;
case '4':
ks.type(KeyEvent.VK_4, character);
break;
case '5':
ks.type(KeyEvent.VK_5, character);
break;
case '6':
ks.type(KeyEvent.VK_6, character);
break;
case '7':
ks.type(KeyEvent.VK_7, character);
break;
case '8':
ks.type(KeyEvent.VK_8, character);
break;
case '9':
ks.type(KeyEvent.VK_9, character);
break;
case '-':
ks.type(KeyEvent.VK_MINUS, character);
break;
case '=':
ks.type(KeyEvent.VK_EQUALS, character);
break;
case '~':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_BACK_QUOTE, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case '!':
ks.type(KeyEvent.VK_EXCLAMATION_MARK, character);
break;
case '@':
ks.type(KeyEvent.VK_AT, character);
break;
case '#':
ks.type(KeyEvent.VK_NUMBER_SIGN, character);
break;
case '$':
ks.type(KeyEvent.VK_DOLLAR, character);
break;
case '%':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_5, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case '^':
ks.type(KeyEvent.VK_CIRCUMFLEX, character);
break;
case '&':
ks.type(KeyEvent.VK_AMPERSAND, character);
break;
case '*':
ks.type(KeyEvent.VK_ASTERISK, character);
break;
case '(':
ks.type(KeyEvent.VK_LEFT_PARENTHESIS, character);
break;
case ')':
ks.type(KeyEvent.VK_RIGHT_PARENTHESIS, character);
break;
case '_':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_MINUS, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case '+':
ks.type(KeyEvent.VK_PLUS, character);
break;
case '\t':
ks.type(KeyEvent.VK_TAB, character);
break;
case '\n':
ks.type(KeyEvent.VK_ENTER, character);
break;
case '[':
ks.type(KeyEvent.VK_OPEN_BRACKET, character);
break;
case ']':
ks.type(KeyEvent.VK_CLOSE_BRACKET, character);
break;
case '\\':
ks.type(KeyEvent.VK_BACK_SLASH, character);
break;
case '{':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_OPEN_BRACKET, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case '}':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_CLOSE_BRACKET, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case '|':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_BACK_SLASH, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case ';':
ks.type(KeyEvent.VK_SEMICOLON, character);
break;
case ':':
ks.type(KeyEvent.VK_COLON, character);
break;
case '\'':
ks.type(KeyEvent.VK_QUOTE, character);
break;
case '"':
ks.type(KeyEvent.VK_QUOTEDBL, character);
break;
case ',':
ks.type(KeyEvent.VK_COMMA, character);
break;
case '<':
ks.type(KeyEvent.VK_LESS, character);
break;
case '.':
ks.type(KeyEvent.VK_PERIOD, character);
break;
case '>':
ks.type(KeyEvent.VK_GREATER, character);
break;
case '/':
ks.type(KeyEvent.VK_SLASH, character);
break;
case '?':
ks.press(KeyEvent.VK_SHIFT, '\0');
ks.type(KeyEvent.VK_SLASH, character);
ks.release(KeyEvent.VK_SHIFT, '\0');
break;
case ' ':
ks.type(KeyEvent.VK_SPACE, character);
break;
default:
throw new IllegalArgumentException("Cannot type character " + character);
}
}
public static class KeySequence implements Iterable<Key> {
private List<Key> keys;
public KeySequence() {
keys = new ArrayList<>(25);
}
public void type(int keyCode, char keyChar) {
keys.add(new Key(Key.StrokeType.Type, keyCode, keyChar));
}
public void press(int keyCode, char keyChar) {
keys.add(new Key(Key.StrokeType.Press, keyCode, keyChar));
}
public void release(int keyCode, char keyChar) {
keys.add(new Key(Key.StrokeType.Release, keyCode, keyChar));
}
public Iterator<Key> iterator() {
return keys.iterator();
}
}
}
关于java - 如何完美模拟 KeyEvents?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14572270/
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚
Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack
在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/
我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为
我有一大串格式化数据(例如JSON),我想使用Psychinruby同时保留格式转储到YAML。基本上,我希望JSON使用literalstyle出现在YAML中:---json:|{"page":1,"results":["item","another"],"total_pages":0}但是,当我使用YAML.dump时,它不使用文字样式。我得到这样的东西:---json:!"{\n\"page\":1,\n\"results\":[\n\"item\",\"another\"\n],\n\"total_pages\":0\n}\n"我如何告诉Psych以想要的样式转储标量?解